Skip to content

Commit cc64ca2

Browse files
committed
Reader fixes after fuzz testing runs
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent fe161fe commit cc64ca2

8 files changed

Lines changed: 64 additions & 5 deletions

File tree

pkg/lang/set.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,26 @@ func CreatePersistentTreeSetWithComparator(comparator IFn, keys ISeq) interface{
2525
}
2626

2727
func NewSet(vals ...interface{}) *Set {
28+
set, err := NewSet2(vals...)
29+
if err != nil {
30+
panic(err)
31+
}
32+
return set
33+
}
34+
35+
func NewSet2(vals ...interface{}) (*Set, error) {
2836
// check for duplicates
2937
for i := 0; i < len(vals); i++ {
3038
for j := i + 1; j < len(vals); j++ {
3139
if Equiv(vals[i], vals[j]) {
32-
panic(NewIllegalArgumentError(fmt.Sprintf("duplicate key: %v", vals[i])))
40+
return nil, NewIllegalArgumentError(fmt.Sprintf("duplicate key: %v", vals[i]))
3341
}
3442
}
3543
}
3644

3745
return &Set{
3846
vals: vals,
39-
}
47+
}, nil
4048
}
4149

4250
var (

pkg/lang/symbol.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package lang
22

33
import (
44
"fmt"
5+
"regexp"
56
"strings"
67
)
78

@@ -11,6 +12,10 @@ type Symbol struct {
1112
name string
1213
}
1314

15+
var (
16+
symbolRegex = regexp.MustCompile(`^(?:[^0-9/].*/)?(?:/|[^0-9/][^/]*)$`)
17+
)
18+
1419
// NewSymbol creates a new symbol.
1520
func NewSymbol(s string) *Symbol {
1621
ns, name := "", s
@@ -83,6 +88,9 @@ func isValidSymbol(ns, name string) bool {
8388
} else {
8489
full = ns + "/" + name
8590
}
91+
if !symbolRegex.MatchString(full) {
92+
return false
93+
}
8694

8795
// early special case for the division operator /
8896
if full == "/" {
@@ -97,11 +105,11 @@ func isValidSymbol(ns, name string) bool {
97105
// empty namespace
98106
return false
99107
}
100-
if strings.HasSuffix(name, ":") {
108+
if strings.HasSuffix(name, ":") || strings.HasSuffix(ns, ":") {
101109
// name ends with a colon (match clojure)
102110
return false
103111
}
104-
if strings.Contains(name, "::") {
112+
if strings.Contains(full, "::") {
105113
// name contains double colon
106114
//
107115
// NB: clojure reader rejects this, but clojure.core/symbol

pkg/reader/reader.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"math"
8+
"math/big"
89
"regexp"
910
"strconv"
1011
"strings"
@@ -191,6 +192,12 @@ type (
191192
posStack []pos
192193

193194
pendingForms []any
195+
196+
// nested syntax quoting grows forms exponentially, so we
197+
// limit the depth to prevent DoS.
198+
//
199+
// Found by the fuzz tester.
200+
syntaxQuoteNestCounter int
194201
}
195202
)
196203

@@ -512,7 +519,11 @@ func (r *Reader) readSet() (interface{}, error) {
512519
if err != nil {
513520
return nil, err
514521
}
515-
return lang.NewSet(vals...), nil
522+
set, err := lang.NewSet2(vals...)
523+
if err != nil {
524+
return nil, r.error("invalid set: %w", err)
525+
}
526+
return set, nil
516527
}
517528

518529
func (r *Reader) readString() (interface{}, error) {
@@ -724,6 +735,14 @@ func (r *Reader) readQuote() (interface{}, error) {
724735
}
725736

726737
func (r *Reader) readSyntaxQuote() (interface{}, error) {
738+
if r.syntaxQuoteNestCounter > 10 {
739+
return nil, r.error("syntax-quote nesting too deep")
740+
}
741+
r.syntaxQuoteNestCounter++
742+
defer func() {
743+
r.syntaxQuoteNestCounter--
744+
}()
745+
727746
node, err := r.readExpr(false, 0)
728747
if err != nil {
729748
return nil, err
@@ -1114,6 +1133,12 @@ func (r *Reader) readNumber(numStr string) (interface{}, error) {
11141133
if err != nil {
11151134
return nil, r.error("invalid ratio: %s", numStr)
11161135
}
1136+
1137+
// if denom is 0, error
1138+
if denomBig.ToBigInteger().Cmp(big.NewInt(0)) == 0 {
1139+
return nil, r.error("divide by zero")
1140+
}
1141+
11171142
return lang.NewRatioBigInt(numBig, denomBig), nil
11181143
}
11191144

@@ -1291,6 +1316,14 @@ func (r *Reader) readConditional(eofOK bool, stopRune rune) (any, error) {
12911316
return nil, r.error("splicing read-cond form must be seqable")
12921317
}
12931318
seq := seqable.Seq()
1319+
if seq == nil {
1320+
// return the next expression (not nil!)
1321+
form, err := r.readExpr(eofOK, stopRune)
1322+
if err != nil {
1323+
return nil, err
1324+
}
1325+
return form, nil
1326+
}
12941327
first := seq.First()
12951328
for seq = seq.Next(); seq != nil; seq = seq.Next() {
12961329
r.pendingForms = append(r.pendingForms, seq.First())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
string("#{0 0}")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
string("0/0")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
string("##```````````````````````s#")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
string("0#?@(:glj()0)")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
string("`A:/0")

0 commit comments

Comments
 (0)