diff --git a/src/core/peg.c b/src/core/peg.c index 33fd52c8..5ed969c6 100644 --- a/src/core/peg.c +++ b/src/core/peg.c @@ -1756,7 +1756,12 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { i += 4; break; case RULE_ARGUMENT: - /* [searchtag, tag] */ + /* [argument-index, tag] */ + OVERFLOW_CHECK(3); + /* index is read as a signed int32 at match time and only + * bounds-checked against extrac on the high side, so a + * negative index would read before extrav */ + if (((int32_t) rule[1]) < 0) goto bad; i += 3; break; case RULE_GETTAG: diff --git a/test/suite-marsh.janet b/test/suite-marsh.janet index b4fdacba..d957350b 100644 --- a/test/suite-marsh.janet +++ b/test/suite-marsh.janet @@ -137,6 +137,23 @@ \xA8\xDE\xDE\xDE\xDE\xDE\xDE\0\0\0\xDE\xDE_unmarshal_testcase3.ja neldb\0\0\0\xD8\x05printG\x01\0\xDE\xDE\xDE'\x03\0marshal_tes/\x02 \0\0\0\0\0*\xFE\x01\04\x02\0\0'\x03\0\r\0\r\0\r\0\r" load-image-dict)) +# Reject peg bytecode with a negative (argument n) index - the index is read as +# a signed int32 at match time and only bounds-checked on the high side, so a +# negative value would read before the argument array. +(do + (def b (buffer (marshal (peg/compile ~(argument 0))))) + (var idx nil) + (for i 0 (- (length b) 2) + (when (and (= (b i) 0x0f) (= (b (+ i 1)) 0x00) (= (b (+ i 2)) 0x00)) + (set idx i))) + (assert idx "peg argument opcode not found") + (def tampered + (buffer/push (buffer (buffer/slice b 0 (+ idx 1))) + "\xCD\xFA\x0A\x1F\x00" + (buffer/slice b (+ idx 2)))) + (assert-error "unmarshal peg negative argument index" + (peg/match (unmarshal tampered) "x" 0 :arg))) + # XXX: still needed? see 72beeeea (gccollect)