Skip to content

Commit

Permalink
Limit the maximum size of string/bytes values (#121)
Browse files Browse the repository at this point in the history
* add tengo.MaxStringLen and tengo.MaxBytesLen to limit the maximum byte-length of string/bytes values

* add couple more tests
  • Loading branch information
d5 authored Mar 1, 2019
1 parent 880ee04 commit 0c5e80b
Show file tree
Hide file tree
Showing 22 changed files with 600 additions and 96 deletions.
20 changes: 20 additions & 0 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"reflect"

"github.com/d5/tengo"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
Expand Down Expand Up @@ -195,6 +196,10 @@ func (c *Compiler) Compile(node ast.Node) error {
}

case *ast.StringLit:
if len(node.Value) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}

c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))

case *ast.CharLit:
Expand Down Expand Up @@ -332,6 +337,9 @@ func (c *Compiler) Compile(node ast.Node) error {
case *ast.MapLit:
for _, elt := range node.Elements {
// key
if len(elt.Key) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))

// value
Expand Down Expand Up @@ -507,6 +515,10 @@ func (c *Compiler) Compile(node ast.Node) error {

case *ast.ImportExpr:
if c.builtinModules[node.ModuleName] {
if len(node.ModuleName) > tengo.MaxStringLen {
return c.error(node, objects.ErrStringLimit)
}

c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
c.emit(node, OpGetBuiltinModule)
} else {
Expand Down Expand Up @@ -610,6 +622,14 @@ func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *Symbo
return child
}

func (c *Compiler) error(node ast.Node, err error) error {
return &Error{
fileSet: c.file.Set(),
node: node,
error: err,
}
}

func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
return &Error{
fileSet: c.file.Set(),
Expand Down
14 changes: 14 additions & 0 deletions objects/builtin_convert.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package objects

import "github.com/d5/tengo"

func builtinString(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
Expand All @@ -12,6 +14,10 @@ func builtinString(args ...Object) (Object, error) {

v, ok := ToString(args[0])
if ok {
if len(v) > tengo.MaxStringLen {
return nil, ErrStringLimit
}

return &String{Value: v}, nil
}

Expand Down Expand Up @@ -117,11 +123,19 @@ func builtinBytes(args ...Object) (Object, error) {

// bytes(N) => create a new bytes with given size N
if n, ok := args[0].(*Int); ok {
if n.Value > int64(tengo.MaxBytesLen) {
return nil, ErrBytesLimit
}

return &Bytes{Value: make([]byte, int(n.Value))}, nil
}

v, ok := ToByteSlice(args[0])
if ok {
if len(v) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}

return &Bytes{Value: v}, nil
}

Expand Down
6 changes: 6 additions & 0 deletions objects/builtin_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package objects

import (
"encoding/json"

"github.com/d5/tengo"
)

// to_json(v object) => bytes
Expand All @@ -15,6 +17,10 @@ func builtinToJSON(args ...Object) (Object, error) {
return &Error{Value: &String{Value: err.Error()}}, nil
}

if len(res) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}

return &Bytes{Value: res}, nil
}

Expand Down
10 changes: 9 additions & 1 deletion objects/builtin_print.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package objects

import (
"fmt"

"github.com/d5/tengo"
)

// print(args...)
Expand Down Expand Up @@ -71,5 +73,11 @@ func builtinSprintf(args ...Object) (Object, error) {
formatArgs[idx] = objectToInterface(arg)
}

return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil
s := fmt.Sprintf(format.Value, formatArgs...)

if len(s) > tengo.MaxStringLen {
return nil, ErrStringLimit
}

return &String{Value: s}, nil
}
5 changes: 5 additions & 0 deletions objects/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package objects
import (
"bytes"

"github.com/d5/tengo"
"github.com/d5/tengo/compiler/token"
)

Expand All @@ -27,6 +28,10 @@ func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
case token.Add:
switch rhs := rhs.(type) {
case *Bytes:
if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}

return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
}
}
Expand Down
8 changes: 8 additions & 0 deletions objects/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strconv"
"time"

"github.com/d5/tengo"
)

// ToString will try to convert object o to string value.
Expand Down Expand Up @@ -194,6 +196,9 @@ func FromInterface(v interface{}) (Object, error) {
case nil:
return UndefinedValue, nil
case string:
if len(v) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: v}, nil
case int64:
return &Int{Value: v}, nil
Expand All @@ -211,6 +216,9 @@ func FromInterface(v interface{}) (Object, error) {
case float64:
return &Float{Value: v}, nil
case []byte:
if len(v) > tengo.MaxBytesLen {
return nil, ErrBytesLimit
}
return &Bytes{Value: v}, nil
case error:
return &Error{Value: &String{Value: v.Error()}}, nil
Expand Down
6 changes: 6 additions & 0 deletions objects/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ var ErrInvalidOperator = errors.New("invalid operator")
// ErrWrongNumArguments represents a wrong number of arguments error.
var ErrWrongNumArguments = errors.New("wrong number of arguments")

// ErrBytesLimit represents an error where the size of bytes value exceeds the limit.
var ErrBytesLimit = errors.New("exceeding bytes size limit")

// ErrStringLimit represents an error where the size of string value exceeds the limit.
var ErrStringLimit = errors.New("exceeding string size limit")

// ErrInvalidArgumentType represents an invalid argument value type error.
type ErrInvalidArgumentType struct {
Name string
Expand Down
10 changes: 9 additions & 1 deletion objects/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package objects
import (
"strconv"

"github.com/d5/tengo"
"github.com/d5/tengo/compiler/token"
)

Expand All @@ -28,9 +29,16 @@ func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) {
case token.Add:
switch rhs := rhs.(type) {
case *String:
if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: o.Value + rhs.Value}, nil
default:
return &String{Value: o.Value + rhs.String()}, nil
rhsStr := rhs.String()
if len(o.Value)+len(rhsStr) > tengo.MaxStringLen {
return nil, ErrStringLimit
}
return &String{Value: o.Value + rhsStr}, nil
}
}

Expand Down
4 changes: 2 additions & 2 deletions runtime/vm_assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ out = func() {
`, 136)

// assigning different type value
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
expect(t, `
out = func() {
a := 5
Expand Down
15 changes: 15 additions & 0 deletions runtime/vm_builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package runtime_test
import (
"testing"

"github.com/d5/tengo"
"github.com/d5/tengo/objects"
)

Expand Down Expand Up @@ -193,3 +194,17 @@ func TestBuiltinFunction(t *testing.T) {
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure
expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object
}

func TestBytesN(t *testing.T) {
curMaxBytesLen := tengo.MaxBytesLen
defer func() { tengo.MaxBytesLen = curMaxBytesLen }()
tengo.MaxBytesLen = 10

expect(t, `out = bytes(0)`, make([]byte, 0))
expect(t, `out = bytes(10)`, make([]byte, 10))
expectError(t, `bytes(11)`, "bytes size limit")

tengo.MaxBytesLen = 1000
expect(t, `out = bytes(1000)`, make([]byte, 1000))
expectError(t, `bytes(1001)`, "bytes size limit")
}
Loading

0 comments on commit 0c5e80b

Please sign in to comment.