Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This commit fixes a potential denial of service vulnerability in logrus.Writer() that could be triggered by logging text longer than 64kb without newlines. #1376

Merged
merged 3 commits into from
May 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 33 additions & 1 deletion writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"io"
"runtime"
"strings"
)

// Writer at INFO level. See WriterLevel for details.
Expand All @@ -20,15 +21,18 @@ func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
return NewEntry(logger).WriterLevel(level)
}

// Writer returns an io.Writer that writes to the logger at the info log level
func (entry *Entry) Writer() *io.PipeWriter {
return entry.WriterLevel(InfoLevel)
}

// WriterLevel returns an io.Writer that writes to the logger at the given log level
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
reader, writer := io.Pipe()

var printFunc func(args ...interface{})

// Determine which log function to use based on the specified log level
switch level {
case TraceLevel:
printFunc = entry.Trace
Expand All @@ -48,23 +52,51 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
printFunc = entry.Print
}

// Start a new goroutine to scan the input and write it to the logger using the specified print function.
// It splits the input into chunks of up to 64KB to avoid buffer overflows.
go entry.writerScanner(reader, printFunc)

// Set a finalizer function to close the writer when it is garbage collected
runtime.SetFinalizer(writer, writerFinalizer)

return writer
}

// writerScanner scans the input from the reader and writes it to the logger
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
scanner := bufio.NewScanner(reader)

// Set the buffer size to the maximum token size to avoid buffer overflows
scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), bufio.MaxScanTokenSize)

// Define a split function to split the input into chunks of up to 64KB
chunkSize := 64 * 1024 // 64KB
splitFunc := func(data []byte, atEOF bool) (int, []byte, error) {
if len(data) > chunkSize {
return chunkSize, data[:chunkSize], nil
}

return len(data), data, nil
}

//Use the custom split function to split the input
scanner.Split(splitFunc)

// Scan the input and write it to the logger using the specified print function
for scanner.Scan() {
printFunc(scanner.Text())
printFunc(strings.TrimRight(scanner.Text(), "\r\n"))
}

// If there was an error while scanning the input, log an error
if err := scanner.Err(); err != nil {
entry.Errorf("Error while reading from Writer: %s", err)
}

// Close the reader when we are done
reader.Close()
}

// WriterFinalizer is a finalizer function that closes then given writer when it is garbage collected
func writerFinalizer(writer *io.PipeWriter) {
writer.Close()
}