Skip to content

expfmt: TextDecoder infinite bufio Read recursion #442

@abursavich

Description

@abursavich

Problem

If the io.Reader passed to expfmt.NewDecoder with the expfmt.FmtText format is a *bufio.Reader, then Decoder.Decode will infinitely recurse in the bufio.(*Reader).Read function.

Explanation

expfmt.(*textDecoder).Decode calls expfmt.(*TextParser).TextToMetricFamilies passing its same io.Reader each time, which is then passed to expfmt.(*TextParser).reset, which contains the following snippet:

	if p.buf == nil {
		p.buf = bufio.NewReader(in)
	} else {
		p.buf.Reset(in)
	}

The problem is that bufio.NewReader will return the given io.Reader without wrapping it, if it happens to already be a *bufio.Reader of sufficient size. All good on the first call from Decode, but the second call ends up setting the *bufio.Reader as its own underlying io.Reader and we get an infinite recursion.

A reasonable person might argue that this is a bug in bufio, but we can still fix it in expfmt.

Repro

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
)

const example = `
# TYPE foo gauge
foo 0
`

func main() {
	r := bufio.NewReader(strings.NewReader(example))
	dec := expfmt.NewDecoder(r, expfmt.FmtText)
	for {
		var mf dto.MetricFamily
		if err := dec.Decode(&mf); err != nil {
			if err == io.EOF {
				return
			}
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions