santhosh-tekuri/jsonschema

Malformed `json.Number` causes panic

chanced opened this issue · 7 comments

package main

import (
	"encoding/json"
	"log"

	"github.com/santhosh-tekuri/jsonschema/v5"
)

func main() {
	schema := `{"type": "integer"}`
	instance := json.Number("abc")

	sch, err := jsonschema.CompileString("schema.json", schema)
	if err != nil {
		log.Fatalf("%#v", err)
	}
	if err = sch.Validate(instance); err != nil {
		log.Fatalf("%#v", err)
	}

}

https://go.dev/play/p/TWAWZJPb0iL

results in:

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x28 pc=0x102f157cc]

goroutine 1 [running]:
github.com/santhosh-tekuri/jsonschema/v5.(*Schema).validateValue.func1()
        /Users/chance/go/pkg/mod/github.com/santhosh-tekuri/jsonschema/v5@v5.0.0/schema.go:151 +0xd4
panic({0x102f67220, 0x1030350c0})
        /opt/homebrew/Cellar/go/1.18.5/libexec/src/runtime/panic.go:838 +0x204
math/big.(*Rat).IsInt(...)
        /opt/homebrew/Cellar/go/1.18.5/libexec/src/math/big/rat.go:402
github.com/santhosh-tekuri/jsonschema/v5.(*Schema).validate(0x14000271880, {0x0, 0x0, 0x0}, 0x0, {0x0, 0x0}, {0x102f69140, 0x140001edb90?}, {0x0, ...})
        /Users/chance/go/pkg/mod/github.com/santhosh-tekuri/jsonschema/v5@v5.0.0/schema.go:242 +0x696c
github.com/santhosh-tekuri/jsonschema/v5.(*Schema).validateValue(0x14000271880, {0x102f69140?, 0x140001edb90?}, {0x0, 0x0})
        /Users/chance/go/pkg/mod/github.com/santhosh-tekuri/jsonschema/v5@v5.0.0/schema.go:155 +0x98
github.com/santhosh-tekuri/jsonschema/v5.(*Schema).Validate(...)
        /Users/chance/go/pkg/mod/github.com/santhosh-tekuri/jsonschema/v5@v5.0.0/schema.go:141
main.main()
        /Users/chance/dev/spike/go/main.go:21 +0x9c
exit status 2

The issue is here:

jsonschema/schema.go

Lines 241 to 245 in e298789

num, _ := new(big.Rat).SetString(fmt.Sprint(v))
if num.IsInt() {
matched = true
break
}

The second return of Rat.SetString is a bool which indicates whether or not it was successful. If unsuccessful, num is nil.

as per doc, json.Number must be valid json number literal. so the library assumes it is valid number. so it is fine to panic if invalid json.Number is given.

InvalidJSONTypeError is used in examples like instance = time.Now(). here instance is of type time.Time which is not valid json type

These docs? It doesn't mention that it must be a valid number. In fact, the accessor / parser methods return an error (as they are just using strconv under the hood) if unable to parse.

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
	return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
	return strconv.ParseInt(string(n), 10, 64)
}

if json.Number is float then int64 returns error

also json.Number may not fit into 64 bit size, so it can return error. we are using big.Rat to handle that.

Understood and I recognize that in a normal decoding flow, it would be caught with the isValidNumber function in the json package.

However, I'm not unmarshaling the data first. There's no reason to attempt to do so before validating. This also doesn't work:

package main

import (
	"encoding/json"
	"log"

	"github.com/santhosh-tekuri/jsonschema/v5"
)

func main() {
	schema := `{"type": "integer"}`
	instance := json.RawMessage("0")

	sch, err := jsonschema.CompileString("schema.json", schema)
	if err != nil {
		log.Fatalf("%v", err)
	}
	if err = sch.Validate(instance); err != nil {
		log.Fatalf("%v", err)
	}

}

https://go.dev/play/p/-ozZtjEAoIW

as it is considered invalid json by this package so I have to use json.Number in this scenario.

Arguably I could validate that it is a number but then that number is going to be checked at least 3 times (by me, by big.Rat, and by json.Unmarshal)

Besides that, I still think it makes sense to clean up the edge-case and prevent a nil-pointer panic. I've already done so in #79

the library works only for valid json types like string number boolean nil.

it does not work for json.RawMessage or any type like time.Time even through they implement encoding.BinaryUnmarshaler interface.

for example, you cannot validate a struct object, even though you have unmarshalled json into struct object.

Arg. I must have been thinking of a different json schema library which operates on []byte or perhaps I'm just incredibly tired (its rather late/early here).

Sorry about that! 🤦‍♂️