Skip to content
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

Reject wasm code exceeding limit #302

Merged
merged 3 commits into from
Nov 9, 2020
Merged

Reject wasm code exceeding limit #302

merged 3 commits into from
Nov 9, 2020

Conversation

alpe
Copy link
Contributor

@alpe alpe commented Nov 6, 2020

Summary of changes:

  • Returns ErrLimit when wasm code exceeds max size
  • New default wasm code max size: 600 * 1024 bytes
  • Max size configurable via parameters
  • Limit also applied to uncompressed wasm code

@alpe alpe requested a review from ethanfrey November 6, 2020 10:52
@codecov
Copy link

codecov bot commented Nov 6, 2020

Codecov Report

Merging #302 (2efd962) into master (41cf73d) will increase coverage by 0.16%.
The diff coverage is 47.82%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #302      +/-   ##
==========================================
+ Coverage   17.23%   17.40%   +0.16%     
==========================================
  Files          35       35              
  Lines       10463    10471       +8     
==========================================
+ Hits         1803     1822      +19     
+ Misses       8563     8551      -12     
- Partials       97       98       +1     
Impacted Files Coverage Δ
x/wasm/internal/types/types.pb.go 0.76% <0.00%> (+<0.01%) ⬆️
x/wasm/internal/types/params.go 60.97% <70.00%> (+2.64%) ⬆️
x/wasm/internal/keeper/ioutil.go 100.00% <100.00%> (ø)
x/wasm/internal/keeper/keeper.go 89.69% <100.00%> (+0.09%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 41cf73d...2efd962. Read the comment docs.

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I am scratching my head how it is sure that limit passes and limit+1 fails. But tests pass for those cases and your understanding of the go stdlib is better than mine.

You can merge as is or attempt to add some more defensive code there if you think my worry is justified.

if l.r.N <= 0 {
return 0, types.ErrLimit
}
return l.r.Read(p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadAll keeps reading until one read returns (0, nil) or any return (x, err) correct?

Update: checked ioutil and bytes.Buffer. They only stop when it hits an error. If the error is EOF, then it stops and return data with no error, otherwise, it returns the error.

Seems fine, just note this returns an error if the input is the exact size of n.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe, so the read, then check if it overflowed? Or maybe I don't fully understand how LimitedReader works.

@@ -28,6 +30,24 @@ func uncompress(src []byte) ([]byte, error) {
return nil, err
}
zr.Multistream(false)
defer zr.Close()
return ioutil.ReadAll(LimitReader(zr, int64(limit)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means if the total size >= limit we will get the types.ErrLimit error. However, we want total_size > limit.
I would use int64(limit + 1) here, so if there is one byte more than the requested limit, it returns an error

@@ -57,31 +60,38 @@ func TestUncompress(t *testing.T) {
src: wasmGzipped[:len(wasmGzipped)-5],
expError: io.ErrUnexpectedEOF,
},
"handle limit gzip output": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, this passes? That means it can return (n, EOF) where n > 0?

The test cases (both limit and limit+1 gzips) pass. So I can approve it. Just seems there is some possible way something can slip through.

@@ -110,6 +110,12 @@ func (k Keeper) getInstantiateAccessConfig(ctx sdk.Context) types.AccessType {
return a
}

func (k Keeper) GetMaxWasmCodeSize(ctx sdk.Context) uint64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, nice to add the parameter here - this is set in genesis, right?

@@ -32,6 +32,7 @@ message Params {
option (gogoproto.goproto_stringer) = false;
AccessConfig code_upload_access = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"code_upload_access\""];
AccessType instantiate_default_permission = 2 [(gogoproto.moretags) = "yaml:\"instantiate_default_permission\""];
uint64 max_wasm_code_size = 3 [(gogoproto.moretags) = "yaml:\"max_wasm_code_size\""];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. Nice addition here.

Reading the config option (1k gas) on upload (> 1 million gas) is virtually 0 overhead. Happy for a param here.

@@ -15,10 +15,13 @@ import (
const (
// DefaultParamspace for params keeper
DefaultParamspace = ModuleName
// DefaultMaxWasmCodeSize limit max bytes read to prevent gzip bombs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice updates here.
You have thought more angles than I did.

💯

@alpe
Copy link
Contributor Author

alpe commented Nov 9, 2020

The underlying (gzip) reader returns n, io.EOF for the last read with a size <=limit. n, EOF are handled in ioutil.ReadAll
The limit reader comes into play only for read > limit where the last call before returned n, nil.
I have added a missing test case for uncompressed bytes.

@alpe alpe merged commit fbd7168 into master Nov 9, 2020
@alpe alpe deleted the code_max_param_289 branch November 9, 2020 08:05
return src, nil
case n > limit:
return nil, types.ErrLimit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check also rejects compressed data > limit, which is smaller than limit when uncompressed. Is this intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good edge case!
This can only happen when gzip does not compress the data but adds it header on top. I guess that is very unlikely to happen and the workaround would be to send uncompressed data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. If found the code a bit confusing to review, because it is not very clear which parts deals with compressed and which with uncompressed data. Some checks operate on both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make Wasm maxSize a configuration option Return error if wasm to big
3 participants