Unverified Commit 6428f6bb authored by Eric Myhre's avatar Eric Myhre Committed by GitHub

Merge pull request #85 from ipld/resource-budget-for-dagcbor-parser

Implement resource budgets in dagcbor parsing.
parents 6a417032 344457a5
...@@ -15,6 +15,12 @@ import ( ...@@ -15,6 +15,12 @@ import (
var ( var (
ErrInvalidMultibase = errors.New("invalid multibase on IPLD link") ErrInvalidMultibase = errors.New("invalid multibase on IPLD link")
ErrAllocationBudgetExceeded = errors.New("message structure demanded too many resources to process")
)
const (
mapEntryGasScore = 8
listEntryGasScore = 4
) )
// This should be identical to the general feature in the parent package, // This should be identical to the general feature in the parent package,
...@@ -22,6 +28,15 @@ var ( ...@@ -22,6 +28,15 @@ var (
// which has dag-cbor's special sauce for detecting schemafree links. // which has dag-cbor's special sauce for detecting schemafree links.
func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error { func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error {
// Have a gas budget, which will be decremented as we allocate memory, and an error returned when execeeded (or about to be exceeded).
// This is a DoS defense mechanism.
// It's *roughly* in units of bytes (but only very, VERY roughly) -- it also treats words as 1 in many cases.
// FUTURE: this ought be configurable somehow. (How, and at what granularity though?)
var gas int = 1048576 * 10
return unmarshal1(na, tokSrc, &gas)
}
func unmarshal1(na ipld.NodeAssembler, tokSrc shared.TokenSource, gas *int) error {
var tk tok.Token var tk tok.Token
done, err := tokSrc.Step(&tk) done, err := tokSrc.Step(&tk)
if err != nil { if err != nil {
...@@ -30,12 +45,12 @@ func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error { ...@@ -30,12 +45,12 @@ func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error {
if done && !tk.Type.IsValue() { if done && !tk.Type.IsValue() {
return fmt.Errorf("unexpected eof") return fmt.Errorf("unexpected eof")
} }
return unmarshal(na, tokSrc, &tk) return unmarshal2(na, tokSrc, &tk, gas)
} }
// starts with the first token already primed. Necessary to get recursion // starts with the first token already primed. Necessary to get recursion
// to flow right without a peek+unpeek system. // to flow right without a peek+unpeek system.
func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) error { func unmarshal2(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token, gas *int) error {
// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want). // FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
switch tk.Type { switch tk.Type {
case tok.TMapOpen: case tok.TMapOpen:
...@@ -44,6 +59,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -44,6 +59,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
if tk.Length == -1 { if tk.Length == -1 {
expectLen = math.MaxInt32 expectLen = math.MaxInt32
allocLen = 0 allocLen = 0
} else {
if *gas-allocLen < 0 { // halt early if this will clearly demand too many resources
return ErrAllocationBudgetExceeded
}
} }
ma, err := na.BeginMap(allocLen) ma, err := na.BeginMap(allocLen)
if err != nil { if err != nil {
...@@ -62,6 +81,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -62,6 +81,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
} }
return ma.Finish() return ma.Finish()
case tok.TString: case tok.TString:
*gas -= len(tk.Str) + mapEntryGasScore
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
// continue // continue
default: default:
return fmt.Errorf("unexpected %s token while expecting map key", tk.Type) return fmt.Errorf("unexpected %s token while expecting map key", tk.Type)
...@@ -74,7 +97,7 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -74,7 +97,7 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
if err != nil { // return in error if the key was rejected if err != nil { // return in error if the key was rejected
return err return err
} }
err = Unmarshal(mva, tokSrc) err = unmarshal1(mva, tokSrc, gas)
if err != nil { // return in error if some part of the recursion errored if err != nil { // return in error if some part of the recursion errored
return err return err
} }
...@@ -87,6 +110,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -87,6 +110,10 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
if tk.Length == -1 { if tk.Length == -1 {
expectLen = math.MaxInt32 expectLen = math.MaxInt32
allocLen = 0 allocLen = 0
} else {
if *gas-allocLen < 0 { // halt early if this will clearly demand too many resources
return ErrAllocationBudgetExceeded
}
} }
la, err := na.BeginList(allocLen) la, err := na.BeginList(allocLen)
if err != nil { if err != nil {
...@@ -105,11 +132,15 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -105,11 +132,15 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
} }
return la.Finish() return la.Finish()
default: default:
*gas -= listEntryGasScore
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
observedLen++ observedLen++
if observedLen > expectLen { if observedLen > expectLen {
return fmt.Errorf("unexpected continuation of array elements beyond declared length") return fmt.Errorf("unexpected continuation of array elements beyond declared length")
} }
err := unmarshal(la.AssembleValue(), tokSrc, tk) err := unmarshal2(la.AssembleValue(), tokSrc, tk, gas)
if err != nil { // return in error if some part of the recursion errored if err != nil { // return in error if some part of the recursion errored
return err return err
} }
...@@ -120,8 +151,16 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -120,8 +151,16 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
case tok.TNull: case tok.TNull:
return na.AssignNull() return na.AssignNull()
case tok.TString: case tok.TString:
*gas -= len(tk.Str)
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignString(tk.Str) return na.AssignString(tk.Str)
case tok.TBytes: case tok.TBytes:
*gas -= len(tk.Bytes)
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
if !tk.Tagged { if !tk.Tagged {
return na.AssignBytes(tk.Bytes) return na.AssignBytes(tk.Bytes)
} }
...@@ -139,12 +178,28 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) ...@@ -139,12 +178,28 @@ func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token)
return fmt.Errorf("unhandled cbor tag %d", tk.Tag) return fmt.Errorf("unhandled cbor tag %d", tk.Tag)
} }
case tok.TBool: case tok.TBool:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignBool(tk.Bool) return na.AssignBool(tk.Bool)
case tok.TInt: case tok.TInt:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignInt(int(tk.Int)) // FIXME overflow check return na.AssignInt(int(tk.Int)) // FIXME overflow check
case tok.TUint: case tok.TUint:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignInt(int(tk.Uint)) // FIXME overflow check return na.AssignInt(int(tk.Uint)) // FIXME overflow check
case tok.TFloat64: case tok.TFloat64:
*gas -= 1
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignFloat(tk.Float64) return na.AssignFloat(tk.Float64)
default: default:
panic("unreachable") panic("unreachable")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment