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

BaseApp Security Improvements #3801

Merged
merged 12 commits into from
Mar 8, 2019
3 changes: 3 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ CLI flag.
* [\#3300] Update the spec-spec, spec file reorg, and TOC updates.
* [\#3694] Push tagged docker images on docker hub when tag is created.
* [\#3716] Update file permissions the client keys directory and contents to `0700`.
* [\#3791] Ensure proper handling of invalid block maximum gas in the `BaseApp`.
* [\#3790] Ensure a valid height is provided by `abci.RequestBeginBlock` in
`BeginBlock`.

### Tendermint

Expand Down
30 changes: 27 additions & 3 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,19 @@ func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams)
mainStore.Set(mainConsensusParamsKey, consensusParamsBz)
}

// getMaximumBlockGas gets the maximum gas from the consensus params.
func (app *BaseApp) getMaximumBlockGas() (maxGas uint64) {
// getMaximumBlockGas gets the maximum gas from the consensus params. It panics
// when the maximum block gas is non-positive.
func (app *BaseApp) getMaximumBlockGas() uint64 {
if app.consensusParams == nil || app.consensusParams.BlockSize == nil {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
return 0
}
return uint64(app.consensusParams.BlockSize.MaxGas)

maxGas := app.consensusParams.BlockSize.MaxGas
if maxGas < 0 {
return 0
}

return uint64(maxGas)
}

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -510,6 +517,19 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
}
}

func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error {
if req.Header.Height < 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think Tendermint starts at height 1 now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, but can the same be said for other consensus engines?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily, but I think we should panic otherwise, because elsewhere in the SDK we assume that the first block height is 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair point -- updated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can a comment be made somewhere or we amend ABCI to stipulate that the first block is at height 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ValarDragon yes, I'll follow up on this.

return fmt.Errorf("invalid height: %d", req.Header.Height)
}

prevHeight := app.LastBlockHeight()
if prevHeight != 0 && req.Header.Height != prevHeight+1 {
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("invalid height: %d; expected: %d", req.Header.Height, prevHeight+1)
}

return nil
}

// BeginBlock implements the ABCI application interface.
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
if app.cms.TracingEnabled() {
Expand All @@ -518,6 +538,10 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
))
}

if err := app.validateHeight(req); err != nil {
panic(err)
}

// Initialize the DeliverTx state. If this is the first block, it should
// already be initialized in InitChain. Otherwise app.deliverState will be
// nil, since it is reset on Commit.
Expand Down
32 changes: 28 additions & 4 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ func TestInitChainer(t *testing.T) {
// stores are mounted and private members are set - sealing baseapp
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
require.Nil(t, err)
require.Equal(t, int64(0), app.LastBlockHeight())

app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty

Expand All @@ -308,6 +309,7 @@ func TestInitChainer(t *testing.T) {

app.Commit()
res = app.Query(query)
require.Equal(t, int64(1), app.LastBlockHeight())
require.Equal(t, value, res.Value)

// reload app
Expand All @@ -316,14 +318,17 @@ func TestInitChainer(t *testing.T) {
app.MountStores(capKey, capKey2)
err = app.LoadLatestVersion(capKey) // needed to make stores non-nil
require.Nil(t, err)
require.Equal(t, int64(1), app.LastBlockHeight())

// ensure we can still query after reloading
res = app.Query(query)
require.Equal(t, value, res.Value)

// commit and ensure we can still query
app.BeginBlock(abci.RequestBeginBlock{})
header := abci.Header{Height: app.LastBlockHeight() + 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
app.Commit()

res = app.Query(query)
require.Equal(t, value, res.Value)
}
Expand Down Expand Up @@ -514,7 +519,6 @@ func TestCheckTx(t *testing.T) {
app := setupBaseApp(t, anteOpt, routerOpt)

nTxs := int64(5)

app.InitChain(abci.RequestInitChain{})

// Create same codec used in txDecoder
Expand Down Expand Up @@ -567,16 +571,22 @@ func TestDeliverTx(t *testing.T) {

nBlocks := 3
txPerHeight := 5

for blockN := 0; blockN < nBlocks; blockN++ {
app.BeginBlock(abci.RequestBeginBlock{})
header := abci.Header{Height: int64(blockN) + 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})

for i := 0; i < txPerHeight; i++ {
counter := int64(blockN*txPerHeight + i)
tx := newTxCounter(counter, counter)

txBytes, err := codec.MarshalBinaryLengthPrefixed(tx)
require.NoError(t, err)

res := app.DeliverTx(txBytes)
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
}

app.EndBlock(abci.RequestEndBlock{})
app.Commit()
}
Expand Down Expand Up @@ -692,7 +702,8 @@ func TestSimulateTx(t *testing.T) {
nBlocks := 3
for blockN := 0; blockN < nBlocks; blockN++ {
count := int64(blockN + 1)
app.BeginBlock(abci.RequestBeginBlock{})
header := abci.Header{Height: count}
app.BeginBlock(abci.RequestBeginBlock{Header: header})

tx := newTxCounter(count, count)
txBytes, err := cdc.MarshalBinaryLengthPrefixed(tx)
Expand Down Expand Up @@ -1216,3 +1227,16 @@ func TestP2PQuery(t *testing.T) {
res = app.Query(idQuery)
require.Equal(t, uint32(4), res.Code)
}

func TestGetMaximumBlockGas(t *testing.T) {
app := setupBaseApp(t)

app.setConsensusParams(&abci.ConsensusParams{BlockSize: &abci.BlockSizeParams{MaxGas: 0}})
require.Equal(t, uint64(0), app.getMaximumBlockGas())

app.setConsensusParams(&abci.ConsensusParams{BlockSize: &abci.BlockSizeParams{MaxGas: 5000000}})
require.Equal(t, uint64(5000000), app.getMaximumBlockGas())

app.setConsensusParams(&abci.ConsensusParams{BlockSize: &abci.BlockSizeParams{MaxGas: -5000000}})
require.Equal(t, uint64(0), app.getMaximumBlockGas())
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
}