Skip to content

Commit 76926c7

Browse files
Fix possible out-of-bounds read in endingToTxtSlice (miekg#1557)
* Update escapedStringOffset to improve readability This function was, admittedly, a little difficult to follow. This new version is slightly more verbose, but, in my opinion, easier to understand. * Fix possible out-of-bounds read in endingToTxtSlice caused by escapedStringOffset If the input had a trailing backslash (normally the start of an escape sequence) with nothing following it, `escapedStringOffset` would return the length of the input, plus one (!), as the result index, causing an out-of-bounds read and panic in `endingToTxtSlice`. Consistent with, e.g., commit 2230854, I've decided to make this an error since it definitely indicates that the string isn't valid. Credit to OSS-Fuzz -- thank you!
1 parent e4ef594 commit 76926c7

File tree

2 files changed

+47
-25
lines changed

2 files changed

+47
-25
lines changed

scan_rr.go

+24-14
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
5555
sx := []string{}
5656
p := 0
5757
for {
58-
i := escapedStringOffset(l.token[p:], 255)
58+
i, ok := escapedStringOffset(l.token[p:], 255)
59+
if !ok {
60+
return nil, &ParseError{err: errstr, lex: l}
61+
}
5962
if i != -1 && p+i != len(l.token) {
6063
sx = append(sx, l.token[p:p+i])
6164
} else {
@@ -1919,29 +1922,36 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
19191922

19201923
// escapedStringOffset finds the offset within a string (which may contain escape
19211924
// sequences) that corresponds to a certain byte offset. If the input offset is
1922-
// out of bounds, -1 is returned.
1923-
func escapedStringOffset(s string, byteOffset int) int {
1924-
if byteOffset == 0 {
1925-
return 0
1925+
// out of bounds, -1 is returned (which is *not* considered an error).
1926+
func escapedStringOffset(s string, desiredByteOffset int) (int, bool) {
1927+
if desiredByteOffset == 0 {
1928+
return 0, true
19261929
}
19271930

1928-
offset := 0
1929-
for i := 0; i < len(s); i++ {
1930-
offset += 1
1931+
currentByteOffset, i := 0, 0
1932+
1933+
for i < len(s) {
1934+
currentByteOffset += 1
19311935

19321936
// Skip escape sequences
19331937
if s[i] != '\\' {
1934-
// Not an escape sequence; nothing to do.
1938+
// Single plain byte, not an escape sequence.
1939+
i++
19351940
} else if isDDD(s[i+1:]) {
1936-
i += 3
1941+
// Skip backslash and DDD.
1942+
i += 4
1943+
} else if len(s[i+1:]) < 1 {
1944+
// No character following the backslash; that's an error.
1945+
return 0, false
19371946
} else {
1938-
i++
1947+
// Skip backslash and following byte.
1948+
i += 2
19391949
}
19401950

1941-
if offset >= byteOffset {
1942-
return i + 1
1951+
if currentByteOffset >= desiredByteOffset {
1952+
return i, true
19431953
}
19441954
}
19451955

1946-
return -1
1956+
return -1, true
19471957
}

scan_test.go

+23-11
Original file line numberDiff line numberDiff line change
@@ -433,25 +433,37 @@ func TestEscapedStringOffset(t *testing.T) {
433433
input string
434434
inputOffset int
435435
expectedOffset int
436+
expectedOK bool
436437
}{
437-
{"simple string with no escape sequences", 20, 20},
438-
{"simple string with no escape sequences", 500, -1},
439-
{`\;\088\\\;\120\\`, 0, 0},
440-
{`\;\088\\\;\120\\`, 1, 2},
441-
{`\;\088\\\;\120\\`, 2, 6},
442-
{`\;\088\\\;\120\\`, 3, 8},
443-
{`\;\088\\\;\120\\`, 4, 10},
444-
{`\;\088\\\;\120\\`, 5, 14},
445-
{`\;\088\\\;\120\\`, 6, 16},
446-
{`\;\088\\\;\120\\`, 7, -1},
438+
{"simple string with no escape sequences", 20, 20, true},
439+
{"simple string with no escape sequences", 500, -1, true},
440+
{`\;\088\\\;\120\\`, 0, 0, true},
441+
{`\;\088\\\;\120\\`, 1, 2, true},
442+
{`\;\088\\\;\120\\`, 2, 6, true},
443+
{`\;\088\\\;\120\\`, 3, 8, true},
444+
{`\;\088\\\;\120\\`, 4, 10, true},
445+
{`\;\088\\\;\120\\`, 5, 14, true},
446+
{`\;\088\\\;\120\\`, 6, 16, true},
447+
{`\;\088\\\;\120\\`, 7, -1, true},
448+
{`\`, 3, 0, false},
449+
{`a\`, 3, 0, false},
450+
{`aa\`, 3, 0, false},
451+
{`aaa\`, 3, 3, true},
452+
{`aaaa\`, 3, 3, true},
447453
}
448454
for i, test := range cases {
449-
outputOffset := escapedStringOffset(test.input, test.inputOffset)
455+
outputOffset, outputOK := escapedStringOffset(test.input, test.inputOffset)
450456
if outputOffset != test.expectedOffset {
451457
t.Errorf(
452458
"Test %d (input %#q offset %d) returned offset %d but expected %d",
453459
i, test.input, test.inputOffset, outputOffset, test.expectedOffset,
454460
)
455461
}
462+
if outputOK != test.expectedOK {
463+
t.Errorf(
464+
"Test %d (input %#q offset %d) returned ok=%t but expected %t",
465+
i, test.input, test.inputOffset, outputOK, test.expectedOK,
466+
)
467+
}
456468
}
457469
}

0 commit comments

Comments
 (0)