move int validation to where a error can be returned

This commit is contained in:
Fabio Bozzo
2024-12-02 11:59:16 +01:00
parent a854389e32
commit 28272e6900
9 changed files with 156 additions and 57 deletions

View File

@@ -9,10 +9,15 @@ import (
"github.com/ipld/go-ipld-prime/must" "github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode" "github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
"github.com/ucan-wg/go-ucan/pkg/policy/selector" "github.com/ucan-wg/go-ucan/pkg/policy/selector"
) )
func FromIPLD(node datamodel.Node) (Policy, error) { func FromIPLD(node datamodel.Node) (Policy, error) {
if err := limits.ValidateIntegerBoundsIPLD(node); err != nil {
return nil, fmt.Errorf("policy contains integer values outside safe bounds: %w", err)
}
return statementsFromIPLD("/", node) return statementsFromIPLD("/", node)
} }

View File

@@ -1,8 +0,0 @@
package limits
const (
// MaxInt53 represents the maximum safe integer in JavaScript (2^53 - 1)
MaxInt53 = 9007199254740991
// MinInt53 represents the minimum safe integer in JavaScript (-2^53 + 1)
MinInt53 = -9007199254740991
)

49
pkg/policy/limits/int.go Normal file
View File

@@ -0,0 +1,49 @@
package limits
import (
"fmt"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/must"
)
const (
// MaxInt53 represents the maximum safe integer in JavaScript (2^53 - 1)
MaxInt53 = 9007199254740991
// MinInt53 represents the minimum safe integer in JavaScript (-2^53 + 1)
MinInt53 = -9007199254740991
)
func ValidateIntegerBoundsIPLD(node ipld.Node) error {
switch node.Kind() {
case ipld.Kind_Int:
val := must.Int(node)
if val > MaxInt53 || val < MinInt53 {
return fmt.Errorf("integer value %d exceeds safe bounds", val)
}
case ipld.Kind_List:
it := node.ListIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return err
}
if err := ValidateIntegerBoundsIPLD(v); err != nil {
return err
}
}
case ipld.Kind_Map:
it := node.MapIterator()
for !it.Done() {
_, v, err := it.Next()
if err != nil {
return err
}
if err := ValidateIntegerBoundsIPLD(v); err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,82 @@
package limits
import (
"testing"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/require"
)
func TestValidateIntegerBoundsIPLD(t *testing.T) {
buildMap := func() datamodel.Node {
nb := basicnode.Prototype.Any.NewBuilder()
qp.Map(1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, "foo", qp.Int(MaxInt53+1))
})(nb)
return nb.Build()
}
buildList := func() datamodel.Node {
nb := basicnode.Prototype.Any.NewBuilder()
qp.List(1, func(la datamodel.ListAssembler) {
qp.ListEntry(la, qp.Int(MinInt53-1))
})(nb)
return nb.Build()
}
tests := []struct {
name string
input datamodel.Node
wantErr bool
}{
{
name: "valid int",
input: basicnode.NewInt(42),
wantErr: false,
},
{
name: "max safe int",
input: basicnode.NewInt(MaxInt53),
wantErr: false,
},
{
name: "min safe int",
input: basicnode.NewInt(MinInt53),
wantErr: false,
},
{
name: "above MaxInt53",
input: basicnode.NewInt(MaxInt53 + 1),
wantErr: true,
},
{
name: "below MinInt53",
input: basicnode.NewInt(MinInt53 - 1),
wantErr: true,
},
{
name: "nested map with invalid int",
input: buildMap(),
wantErr: true,
},
{
name: "nested list with invalid int",
input: buildList(),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateIntegerBoundsIPLD(tt.input)
if tt.wantErr {
require.Error(t, err)
require.Contains(t, err.Error(), "exceeds safe bounds")
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -69,7 +69,11 @@ func Any(v any) (res ipld.Node, err error) {
case string: case string:
return basicnode.NewString(val), nil return basicnode.NewString(val), nil
case int: case int:
return basicnode.NewInt(int64(val)), nil i := int64(val)
if i > limits.MaxInt53 || i < limits.MinInt53 {
return nil, fmt.Errorf("integer value %d exceeds safe integer bounds", i)
}
return basicnode.NewInt(i), nil
case int8: case int8:
return basicnode.NewInt(int64(val)), nil return basicnode.NewInt(int64(val)), nil
case int16: case int16:
@@ -77,6 +81,9 @@ func Any(v any) (res ipld.Node, err error) {
case int32: case int32:
return basicnode.NewInt(int64(val)), nil return basicnode.NewInt(int64(val)), nil
case int64: case int64:
if val > limits.MaxInt53 || val < limits.MinInt53 {
return nil, fmt.Errorf("integer value %d exceeds safe integer bounds", val)
}
return basicnode.NewInt(val), nil return basicnode.NewInt(val), nil
case uint: case uint:
return basicnode.NewInt(int64(val)), nil return basicnode.NewInt(int64(val)), nil
@@ -87,6 +94,9 @@ func Any(v any) (res ipld.Node, err error) {
case uint32: case uint32:
return basicnode.NewInt(int64(val)), nil return basicnode.NewInt(int64(val)), nil
case uint64: case uint64:
if val > uint64(limits.MaxInt53) {
return nil, fmt.Errorf("unsigned integer value %d exceeds safe integer bounds", val)
}
return basicnode.NewInt(int64(val)), nil return basicnode.NewInt(int64(val)), nil
case float32: case float32:
return basicnode.NewFloat(float64(val)), nil return basicnode.NewFloat(float64(val)), nil

View File

@@ -8,6 +8,7 @@ import (
cidlink "github.com/ipld/go-ipld-prime/linking/cid" cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/printer" "github.com/ipld/go-ipld-prime/printer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/limits" "github.com/ucan-wg/go-ucan/pkg/policy/limits"
) )
@@ -295,14 +296,11 @@ func TestAnyAssembleIntegerOverflow(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := Any(tt.input)
if tt.shouldErr { if tt.shouldErr {
require.Panics(t, func() { require.Error(t, err)
anyAssemble(tt.input)
})
} else { } else {
require.NotPanics(t, func() { require.NoError(t, err)
anyAssemble(tt.input)
})
} }
}) })
} }

View File

@@ -8,8 +8,6 @@ import (
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/must" "github.com/ipld/go-ipld-prime/must"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
) )
// Match determines if the IPLD node satisfies the policy. // Match determines if the IPLD node satisfies the policy.
@@ -268,10 +266,6 @@ func isOrdered(expected ipld.Node, actual ipld.Node, satisfies func(order int) b
a := must.Int(actual) a := must.Int(actual)
b := must.Int(expected) b := must.Int(expected)
if a > limits.MaxInt53 || a < limits.MinInt53 || b > limits.MaxInt53 || b < limits.MinInt53 {
return false
}
return satisfies(cmp.Compare(a, b)) return satisfies(cmp.Compare(a, b))
} }

View File

@@ -11,7 +11,6 @@ import (
"github.com/ipld/go-ipld-prime/node/basicnode" "github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
"github.com/ucan-wg/go-ucan/pkg/policy/literal" "github.com/ucan-wg/go-ucan/pkg/policy/literal"
) )
@@ -902,38 +901,3 @@ func TestPartialMatch(t *testing.T) {
}) })
} }
} }
func TestIntegerOverflow(t *testing.T) {
tests := []struct {
name string
expected ipld.Node
actual ipld.Node
want bool
}{
{
name: "valid integers",
expected: literal.Int(42),
actual: literal.Int(43),
want: true, // for gt comparison
},
{
name: "exceeds MaxInt53",
expected: literal.Int(limits.MaxInt53 + 1),
actual: literal.Int(42),
want: false,
},
{
name: "below MinInt53",
expected: literal.Int(limits.MinInt53 - 1),
actual: literal.Int(42),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isOrdered(tt.expected, tt.actual, gt)
require.Equal(t, tt.want, result)
})
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp" "github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basicnode" "github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ucan-wg/go-ucan/pkg/policy/limits"
) )
// Selector describes a UCAN policy selector, as specified here: // Selector describes a UCAN policy selector, as specified here:
@@ -22,6 +23,10 @@ type Selector []segment
// - a resolutionerr error if not being able to resolve to a node // - a resolutionerr error if not being able to resolve to a node
// - nil and no errors, if the selector couldn't match on an optional segment (with ?). // - nil and no errors, if the selector couldn't match on an optional segment (with ?).
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) { func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
if err := limits.ValidateIntegerBoundsIPLD(subject); err != nil {
return nil, fmt.Errorf("node contains integer values outside safe bounds: %w", err)
}
return resolve(s, subject, nil) return resolve(s, subject, nil)
} }