Skip to content

Commit 2669887

Browse files
holimanshekhirin
authored andcommitted
log: better sanitation (ethereum#26556)
1 parent bb763e4 commit 2669887

File tree

2 files changed

+78
-6
lines changed

2 files changed

+78
-6
lines changed

log/format.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type TerminalStringer interface {
8686
// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
8787
func TerminalFormat(usecolor bool) Format {
8888
return FormatFunc(func(r *Record) []byte {
89+
msg := escapeMessage(r.Msg)
8990
var color = 0
9091
if usecolor {
9192
switch r.Lvl {
@@ -122,19 +123,19 @@ func TerminalFormat(usecolor bool) Format {
122123

123124
// Assemble and print the log heading
124125
if color > 0 {
125-
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg)
126+
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, msg)
126127
} else {
127-
fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg)
128+
fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, msg)
128129
}
129130
} else {
130131
if color > 0 {
131-
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg)
132+
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg)
132133
} else {
133-
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg)
134+
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg)
134135
}
135136
}
136137
// try to justify the log output for short messages
137-
length := utf8.RuneCountInString(r.Msg)
138+
length := utf8.RuneCountInString(msg)
138139
if len(r.Ctx) > 0 && length < termMsgJust {
139140
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
140141
}
@@ -167,6 +168,8 @@ func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) {
167168
v := formatLogfmtValue(ctx[i+1], term)
168169
if !ok {
169170
k, v = errorKey, formatLogfmtValue(k, term)
171+
} else {
172+
k = escapeString(k)
170173
}
171174

172175
// XXX: we should probably check that all of your key bytes aren't invalid
@@ -471,7 +474,7 @@ func formatLogfmtBigInt(n *big.Int) string {
471474
func escapeString(s string) string {
472475
needsQuoting := false
473476
for _, r := range s {
474-
// We quote everything below " (0x34) and above~ (0x7E), plus equal-sign
477+
// We quote everything below " (0x22) and above~ (0x7E), plus equal-sign
475478
if r <= '"' || r > '~' || r == '=' {
476479
needsQuoting = true
477480
break
@@ -482,3 +485,26 @@ func escapeString(s string) string {
482485
}
483486
return strconv.Quote(s)
484487
}
488+
489+
// escapeMessage checks if the provided string needs escaping/quoting, similarly
490+
// to escapeString. The difference is that this method is more lenient: it allows
491+
// for spaces and linebreaks to occur without needing quoting.
492+
func escapeMessage(s string) string {
493+
needsQuoting := false
494+
for _, r := range s {
495+
// Carriage return and Line feed are ok
496+
if r == 0xa || r == 0xd {
497+
continue
498+
}
499+
// We quote everything below <space> (0x20) and above~ (0x7E),
500+
// plus equal-sign
501+
if r < ' ' || r > '~' || r == '=' {
502+
needsQuoting = true
503+
break
504+
}
505+
}
506+
if !needsQuoting {
507+
return s
508+
}
509+
return strconv.Quote(s)
510+
}

log/format_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package log
22

33
import (
4+
"fmt"
45
"math"
56
"math/big"
67
"math/rand"
8+
"strings"
79
"testing"
810
)
911

@@ -93,3 +95,47 @@ func BenchmarkPrettyUint64Logfmt(b *testing.B) {
9395
sink = FormatLogfmtUint64(rand.Uint64())
9496
}
9597
}
98+
99+
func TestSanitation(t *testing.T) {
100+
msg := "\u001b[1G\u001b[K\u001b[1A"
101+
msg2 := "\u001b \u0000"
102+
msg3 := "NiceMessage"
103+
msg4 := "Space Message"
104+
msg5 := "Enter\nMessage"
105+
106+
for i, tt := range []struct {
107+
msg string
108+
want string
109+
}{
110+
{
111+
msg: msg,
112+
want: fmt.Sprintf("] %q %q=%q\n", msg, msg, msg),
113+
},
114+
{
115+
msg: msg2,
116+
want: fmt.Sprintf("] %q %q=%q\n", msg2, msg2, msg2),
117+
},
118+
{
119+
msg: msg3,
120+
want: fmt.Sprintf("] %s %s=%s\n", msg3, msg3, msg3),
121+
},
122+
{
123+
msg: msg4,
124+
want: fmt.Sprintf("] %s %q=%q\n", msg4, msg4, msg4),
125+
},
126+
{
127+
msg: msg5,
128+
want: fmt.Sprintf("] %s %q=%q\n", msg5, msg5, msg5),
129+
},
130+
} {
131+
var (
132+
logger = New()
133+
out = new(strings.Builder)
134+
)
135+
logger.SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(out, TerminalFormat(false))))
136+
logger.Info(tt.msg, tt.msg, tt.msg)
137+
if have := out.String()[24:]; tt.want != have {
138+
t.Fatalf("test %d: want / have: \n%v\n%v", i, tt.want, have)
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)