-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(scale): implement max length constraint for decoding bytes #2676
Conversation
Codecov Report
@@ Coverage Diff @@
## development #2676 +/- ##
===============================================
+ Coverage 62.98% 62.99% +0.01%
===============================================
Files 211 211
Lines 27136 27155 +19
===============================================
+ Hits 17091 17107 +16
- Misses 8482 8484 +2
- Partials 1563 1564 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
@@ -56,8 +56,10 @@ func indirect(dstv reflect.Value) (elem reflect.Value) { | |||
return | |||
} | |||
|
|||
const defaultMaxLength = 16 * 1024 * 1024 * 1024 // 16GB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest we don't limit the decoding by default. I think that's also why the scale spec doesn't specify a limit, since 20GB of scale data is still, well, valid.
Also if the writer is a file, 16GB+ is fine. The problem is with memory, depending on the environment.
// Unmarshal takes data and a destination pointer to unmarshal the data to. | ||
func Unmarshal(data []byte, dst interface{}) (err error) { | ||
func Unmarshal(data []byte, dst interface{}, options ...func(*Decoder)) (err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- You should define a type
type Option func(decoder *Decoder)
at global scope - Actually that option function should return an error in case of future conflicting options for example (since it's meant to be a stable package go API). The error should be checked in the
for _, option := range options {
loop
} | ||
return | ||
} | ||
|
||
// OptionMaxLength sets the maximum length of the data to be decoded | ||
func OptionMaxLength(maxLength int) func(*Decoder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this argument a uint
, to ensure the user cannot plug in negative values.
type decodeState struct { | ||
io.Reader | ||
maxLength int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's gonna be a niche nit...
getting the size through file.Stat() in Go returns a int64
;
maybe we should have this maxLength
as int64
if i.e. the writer is a file, and the OS is 32 bit (then int
= int32
)
@@ -540,6 +562,9 @@ func (ds *decodeState) decodeBytes(dstv reflect.Value) (err error) { | |||
if err != nil { | |||
return | |||
} | |||
if length > ds.maxLength { | |||
return fmt.Errorf("could not decode length %v greater than max length %v", length, ds.maxLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this a sentinel error. I understand this package doesn't use sentinel errors, but that would help one more step for #2631
return func(options *Decoder) { | ||
options.maxLength = maxLength | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an error would be useful here to prevent the user from settings a limit of 0
for example here.
if !reflect.DeepEqual(dst, tt.in) { | ||
t.Errorf("decodeState.unmarshal() = %#v, want %#v", dst, tt.in) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use assert.Equal
(also the t.Errorf
message is wrong here)
@@ -70,6 +71,103 @@ func Test_decodeState_decodeBytes(t *testing.T) { | |||
} | |||
} | |||
|
|||
var maxLengthDecodeTests = tests{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this a function returning a tests
, so we can be (more) sure there is no funny mutation between each test using it.
} | ||
|
||
func Test_decodeState_decodeBytes_maxLength(t *testing.T) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.Parallel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Do we have places where
Unmarshal
with options is getting used?
I like the idea ofoption
function. It is helpful if we have a bunch of options.
func NewDecoder(r io.Reader) (d *Decoder) { | ||
d = &Decoder{ | ||
decodeState{r}, | ||
func NewDecoder(r io.Reader, options ...func(*Decoder)) (d *Decoder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the option
here for exactly?
Are they like helper functions for decoding? In which case, may be they could be passed only here and be saved as property of decoder
and use them in the Decode
function. If that makes sense, decode function would not need to be passed options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Although you would still have to pass options to scale.Unmarshal
which creates its decoder within AFAIK.
Closed, replaced by PR #2683 |
Changes
decodeState
where error is raised ifdecodeState.decodeLength
is greater thandecodeState.maxLength
to avoid trying to decode very long bytes values.defaultMaxLength
of 16 GB to use as defaultmaxLength
value, so that decoder will not attempt to decode bytes encoded with a length of greater than 16 GB.NewDecoder
andUnmarshal
to accept variadic option, following pattern explained in https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apisOptionMaxLength
for overriding defaultmaxLength
OptionMaxLength
withNewDecoder
andUnmarshal
defaultMaxLength
Tests
go test github.com/ChainSafe/gossamer/pkg/scale/
Issues
fixes #2629, which was causing out of memory errors when trying to decode very long bytes encodings.
Primary Reviewer
@timwu20