github.com/filecoin-project/specs-actors/v4@v4.0.2/docs/post-mortem-jan-21.md (about)

     1  
     2  # Actors severe mainnet bugs post-mortem – Jan 2021
     3  
     4  [**Intro**](#intro)
     5  
     6  [**Consensus Fault Author Check**](#A)
     7  	
     8  - [Part 1: Facts](#facts-A)
     9  - [Part 2: Discussion](#disc-A)
    10  
    11  [**Non-deterministic Map Iteration**](#B)
    12  
    13  - [Part 1: Facts](#facts-B)
    14  - [Part 2: Discussion](#disc-B)
    15  
    16  [**Multisig Infinite Re-entrancy**](#C)
    17  
    18  - [Part 1: Facts](#facts-C)
    19  - [Part 2: Discussion](#disc-C)
    20  
    21  [**Summary discussion and recommendations**](#recc)
    22  
    23  - [What *has* been working for us?](#working)
    24  
    25  
    26  
    27  # Intro
    28  <a name="intro"></a>
    29  
    30  As of January 2021, the actors team is aware of two network-critical issues that have been released into Filecoin mainnet, and one further that turned out to be dangerous but not critical. This document describes the issues and circumstances around their development, and contains notes of discussion about how we might change work practices to reduce the likelihood or impact of other such issues.
    31  
    32  
    33  
    34  *   Consensus fault block author not checked
    35  *   Non-deterministic Go map iteration
    36  *   Multisig infinite re-entrancy
    37  
    38  Some other, less-critical issues, have been uncovered too, but are not explored here.
    39  
    40  The Filecoin Space Race test network transitioned into mainnet on 15 October without a network reset. Shortly prior to that, an on-chain network upgrade upgraded the actors code to release [2.0.3](https://github.com/filecoin-project/specs-actors/releases/tag/v2.0.3) ([code](https://github.com/filecoin-project/specs-actors/tree/v2.0.3)). The prior v0.9 release chain had been considered as pre-mainnet, so this document takes releases 2.0.3 and subsequent as being intended for mainnet.
    41  
    42  
    43  # Consensus Fault Author Check
    44  <a name="A"></a>
    45  
    46  ## Part 1: Facts
    47  <a name="facts-A"></a>
    48  
    49  ### Description of the bug
    50  
    51  The ReportConsensusFault method of the storage miner actor ([code@v2.0.3](https://github.com/filecoin-project/specs-actors/blob/v2.0.3/actors/builtin/miner/miner_actor.go#L1525-L1594)) is intended to be called by any network participant to report a consensus fault committed by the miner. The reporter provides the data of two or three block headers (depending on the fault “type”). The method passes the headers to a runtime syscall VerifyConsensusFault ([code](https://github.com/filecoin-project/specs-actors/blob/v2.0.3/actors/runtime/runtime.go#L187-L196)) which verifies that the headers represent a valid fault, and then applies penalties to the receiving miner and a small reward to the submitter. Penalties include a fee and ineligibility to produce blocks for the next 900 epochs.
    52  
    53  The syscall returns a tuple containing the address of the miner at fault, the epoch of the highest of the blocks comprising it, and the type of fault (or an error, if the fault is not valid). The ReportConsensusFault method **failed to check that the address of the miner at fault matched the receiving actor’s address**, i.e. that the correct miner was being reported. Because of this, any consensus fault specimen could be used to penalize any and all miners on the network. In the extreme case, this could have led to all miners being unable to produce blocks, and a hard-to-recover chain halt.
    54  
    55  
    56  ### Discovery and mitigation
    57  
    58  Community contributor [@zgfzgf](https://github.com/zgfzgf) reported the issue to us on 23 October 2020. 
    59  
    60  There were no instances of this error being exploited. We quietly mitigated the issue in the release of Lotus 1.1.3 Nov 13 2020, by changing the VerifyConsensusFault syscall implementation to require the offending blocks’ signers to be the worker key of the receiving actor. An exploitation at this point would have caused a fork between participants running 1.1.3 and earlier versions. The mitigation was considered to be fully in effect only after Lotus 1.2 was released, which included the network version 7 upgrade (code released Nov 18th 2020 and network upgrade at epoch 265200), and thus forced all participants to be running code containing the workaround.
    61  
    62  We implemented the public fix implemented in [#1314](https://github.com/filecoin-project/specs-actors/pull/1314) on 3 December 2020, which will be launched into the network with actors v3 (estimated early March 2021).
    63  
    64  
    65  ### Origins
    66  
    67  The ReportConsensusFault method was originally implemented and reviewed in non-executable code in an earlier version of the Filecoin spec as [#789](https://github.com/filecoin-project/specs/pull/789) on 9 Jan 2020 in the storage power actor. This [implementation](https://github.com/filecoin-project/specs/blob/218d29ac6c48c342f41ec001787bc158f35e9a35/src/actors/builtin/storage_power/storage_power_actor_code.go#L193-L251) was incomplete, skipping any mechanism to inspect block headers. As a result of [discussion](https://github.com/filecoin-project/specs/pull/789#discussion_r364113492), the target (“slashee”) address was taken as an external parameter that was not verified as corresponding with evidence. The runtime contained no block-header fault verification method. Spec code had no tests.
    68  
    69  Thanks to review we removed this unverified parameter in [#231](https://github.com/filecoin-project/specs-actors/pull/231) on 6 March, after the VerifyConsensusFault syscall existed. In this [implementation](https://github.com/filecoin-project/specs-actors/blob/64ac74fb1cb16077b90715f718a746d55b978deb/actors/builtin/power/power_actor.go#L470-L519), the fault target returned by VerifyConsensusFault identified the offending miner, thus fixing the issue. There were no tests for this method, and #231 didn’t add any.
    70  
    71  We reintroduced the bug in [#271](https://github.com/filecoin-project/specs-actors/pull/271) on 11 April 2020 when moving ReportConsensusFault to the miner actor, as part of a restructuring of pledge accounting from the power to miner actors ([ReportConsensusFault@#271](https://github.com/filecoin-project/specs-actors/blob/889f1a6f9f525ccb6a06cc10858e6917e20bae39/actors/builtin/miner/miner_actor.go#L675-L716)). This change removed all usage of the fault target returned by VerifyConsensusFault, inferring the target to be the receiver but failing to check that they match. There were no miner actor tests, and this PR didn’t add any.
    72  
    73  We introduced the first test of ReportConsensusFault in [#472](https://github.com/filecoin-project/specs-actors/pull/472) on 25 June. This was a happy path test checking that deals were correctly terminated. Other patchwork tests were added over time as other tweaks implemented. The absence of a desirable check could not subsequently be detected through code coverage metrics.
    74  
    75  
    76  ## Part 2: Discussion
    77  <a name="disc-A"></a>
    78  
    79  
    80  
    81  *   The conditions under which the bug was introduced, and re-introduced, are unlikely to recur again (spec author rather than engineer implementer, non-executable code, no initial or existing tests). Large contributions from new contributors might carry similar risk though.
    82  *   There was no spec for the method behaviour, since the 2019 spec development process had favoured code over words.
    83      *   When reviewing, auditors treated the method as a black box.
    84      *   We would expect a spec to have stated this condition explicitly, and so writing either code or tests from such a spec would have caught it.
    85  *   The implicit receiver target is a subtle bug, it’s difficult to detect a missing check.
    86      *   Simple line coverage metrics cannot detect such omissions
    87      *   Casual observers might have considered the VerifyConsensusFault syscall to have done the target check.
    88  *   The team were very methodical about testing many actors, and parts of the miner actor, but didn’t reach that level with miner actor behaviour beyond sector lifecycle. 
    89      *   Time pressure for mainnet launch
    90      *   The miner actor is too big, daunting complexity
    91      *   ReportConsensusFault was not part of code that was changing
    92      *   Miner actor as a whole had lots of tests, effort seemed more profitable elsewhere
    93  *   More critical eyes on the code would have helped, for any reason; testing is one reason
    94  *   Automated and tool-assisted approaches are unlikely to have found this. Mitigation of similar issues in the future depends on design and development process
    95      *   Fuzz testing could have detected this only if it fuzzed the syscall mock return value, which is very unlikely.
    96      *   Fuzz testing at Lotus integration level could possibly have detected it, but unlikely since values would need to be valid consensus-faulty block headers with the same signer but not the receiver
    97      *   Agent-based tests would not have been programmed to try this path
    98  *   **Recommendation**: write a spec (or FIP) in prose ahead of code
    99      *   This involves more eyes and minds thinking about correct behaviour
   100      *   A spec provides an additional guide for test coverage
   101  *   **Recommendation**: require tests to be written concurrently with new code
   102  *   **Recommendation**: increase review breadth to get more eyes on each change
   103  
   104  
   105  # Non-deterministic Map Iteration
   106  <a name="B"></a>
   107  
   108  ## Part 1: Facts
   109  <a name="facts-B"></a>
   110  
   111  ### Description of the bug
   112  
   113  DeadlineSectorMap and PartitionSectorMap are implemented as Go maps with nondeterministic range order.  These two types sort the underlying data before running to achieve deterministic ranging behavior. There was a bug in the sorting function that caused no actual sorting to take place, leading to non deterministic ranging in the same order as the Go map range.
   114  
   115  Four code paths exercised this ranging behavior on both the deadline and partition sectors maps.
   116  
   117  
   118  
   119  1. ProveCommitCallback
   120  2. TerminateSectors
   121  3. DeclareFaults
   122  4. DeclareFaultsRecovered
   123  
   124  For (1) this code path is only hit on CC upgrade. Nondeterminism can only show up when two CC upgrades are scheduled by the same miner in one epoch. The error can occur in (2) when multiple sectors are terminated at once. For (3) or (4) the error could have been triggered by a single DeclareFaults message declaring more than one partition faulty/recovered. 
   125  
   126  (1) is rarely exercised because CC upgrades are rare on mainnet and the bug would require one miner to do enough CC upgrades to get two within the same epoch. (3) is economically deprecated, since as of v2 actors it has been cheaper to implicitly declare faults with a missed post. 
   127  
   128  
   129  ### Discovery and mitigation
   130  
   131  Code path (2) terminating multiple sectors led to a chain head mismatch on December 19th with different orderings resulting in different gas values used.  This caused a disruptive chain halt. We fixed bug in actors v2.3.3, v2 change here: [#1334](https://github.com/filecoin-project/specs-actors/pull/1334) and clearer v3 forward port: [#1335](https://github.com/filecoin-project/specs-actors/pull/1335).  These changes were consumed into lotus and released in version 1.4.0. 
   132  
   133  
   134  ### Origins
   135  
   136  We introduced the bug when refactoring deadline state methods in [#761](https://github.com/filecoin-project/specs-actors/pull/761). We then propagated it into the PR introducing the DeadlineSectorMap and PartitionSectorMap in [#861](https://github.com/filecoin-project/specs-actors/pull/861). This PR includes tests of these abstractions, but they do not cover ranging non-empty collections. There also were no tests checking determinism over non-empty collections, or any existing testing patterns of this sort to build off of.
   137  
   138  We made no significant changes to these abstractions or tests until bug discovery.
   139  
   140  
   141  ## Part 2: Discussion
   142  <a name="disc-B"></a>
   143  
   144  
   145  *   We didn’t discover this one until it affected the network
   146  *   Lotus had not previously supported terminating sectors
   147  *   Error only apparent when terminating _multiple_ deadlines or partitions
   148  *   Non-determinism, including unstable gas usage, is not tested in actors directly
   149      *   It is checked in the VM test vectors, but they target the VM itself, and do not cover a wide range of actor behaviour
   150  *   We could use a “golden file” approach to check for deterministic execution
   151      *   This would also be helpful for detecting unintended behaviour changes between network version upgrades.
   152  *   Mutation testing could have detected this in theory, since the “sorting” line has no effect.
   153      *   We’d need to customize a mutation testing library to work well with actors.
   154  *   The deadline_state_test.go does exercise termination of multiple sectors. This might have caught the issue in combination with a golden file.
   155  *   Simple fuzz testing isn’t helpful; the bug required building up enough state to terminate multiple sectors. 
   156  *   Randomisation over high level agent behaviour could have exercised this (in combination with a golden/expectation file).
   157  *   We’re not aware of anything other than map iteration that could be non-deterministic, but something could be introduced later.
   158      * We explicitly lint against map iteration so introducing map iteration without explicitly indicating an exception will raise a flag. The issue here was that we mae a mistake while implementing a pathway that we explicitly indicated as an exception.
   159  *   **Recommendation**: introduce a golden file (or trace, or other determinism check) to unit, scenario and agent tests.
   160  *   **Recommendation**: expand agent-based testing to range over possible actor behaviours (in combination with golden file).
   161  
   162  
   163  # Multisig Infinite Re-entrancy
   164  <a name="C"></a>
   165  
   166  ## Part 1: Facts
   167  <a name="facts-C"></a>
   168  
   169  ### Description of the bug
   170  
   171  A 1 of n multisig actor could add itself as a signer, propose a message to the multisig actor address such that the proposal message is an approval message to the multisig actor address approving the tx id of the approval message itself. A multisig transaction of this form recurses indefinitely approving the approval message at each iteration.  This was possible because the multisig actor did not remove the tx id from multisig state before launching the send call that executed the transaction.
   172  
   173  Furthermore while executing a nested send the node’s vm adds function call stack frames to the go runtime. Go panics with a stack overflow error when the stack gets to about 1GB.  If the send calls looped deep enough this will cause all nodes on the network to panic creating a difficult to recover network halt.
   174  
   175  Because there was no send call depth limiting in place during initial network launch, this widespread node pancing was a real concern. However each multisig call spends some gas. For the mainnet protocol configuration the gas limit was exceeded with a only a program stack size of around 250 MB making the stack overflow not feasible.
   176  
   177  Since unit tests interact with a mock runtime to fake out all calls to other actors our testing framework was not equipped to exercise this edge case.
   178  
   179  
   180  ### Discovery and mitigation
   181  
   182  During manual testing the day before liftoff on Oct 14 2020, we were alerted that a bug occurred during multisig removal such that a multisig signer could not remove itself. This led us to discover the general reentrancy issue and the associated potential for the infinite looping approve transaction.
   183  
   184  We monitored mainnet for multisig signers that added themselves as signers post liftoff and later assessed that it was not feasible to use the bug to stack overflow and crash all nodes on Oct 17 2020. Furthermore we analyzied all other actor call paths and determined that there was no path to overflowing the stack while remaining under the gas limit systemwide using reentrant sends. We found a less critical DOS vector on Oct 19 2020 which exploited slow ID-address lookup in the case of many nested VM calls. This attack used the same self-approving multisig, but referenced the multisig's robust address to force an ID lookup every send.
   185  
   186  Members of the lotus team landed a fix into lotus removing this DOS vector with a state caching optimization the same day in [#4481](https://github.com/filecoin-project/lotus/pull/4481) released in lotus v1.1.0.
   187  
   188  With the threat to harm the network with multisig looping effectively eliminated the lotus team then introduced a VM call stack depth limit of 4096 on Oct 20 2020[ #4506](https://github.com/filecoin-project/lotus/pull/4506).
   189  
   190  Recursion up to the stack limit can still be triggered on mainnet. We plan to refactor the multisig actor to remove it in the future.
   191  
   192  
   193  ### Origins
   194  
   195  The original spec for the multisig actor [PR #139](https://github.com/filecoin-project/specs/pull/139/files#diff-c8ac5ac03150002fa720d13154770718087f5f480fc72334af18a83582e3d3b2R831) made on Mar 26 2019 does not definitively have this bug because the state storage model is not specified in enough detail. 
   196  
   197  The bug was introduced in Lotus on Aug 5th 2019 during the first multisig actor commit [#105](https://github.com/filecoin-project/lotus/pull/105/files).  This case is more subtle than the final bug because the tx.Approved value is both modified and checked before send and would halt looping if state updates persisted between send calls. However the actor state is not written until after send so this state change isn’t seen in subsequent calls allowing looping.
   198  
   199  The spec team propagated/introduced a variant of the bug into the spec [here](https://github.com/filecoin-project/specs/pull/774/files#diff-3c41c3cc8a036c552bd599ebd6529f29ecb1764304cbd3aa463487da67602d39R165) in PR [#774](https://github.com/filecoin-project/specs/pull/774) on Dec 22nd 2019. The lotus code appears to have been used as a template for this work.  Interestingly the original lotus issue is resolved in this PR because state is saved before Send. However this PR also removes the lotus check validating that the approver has not previously called this method so the correct state saving placement no longer helps stop looping.
   200  
   201  We carried over this bug into the first commit of specs actors ([link](https://github.com/filecoin-project/specs-actors/commit/ac6379d84c1e8d7fac9fada6b15cc010db261e0a#diff-0511acf500959014d9e8b44d971eb00fc87109c11db18b18137408afb374a608R166)) on Jan 8th, 2020.
   202  
   203  
   204  ## Part 2: Discussion
   205  <a name="disc-C"></a>
   206  
   207  
   208  
   209  *   This turned out to be non-critical only because the gas limit restricted stack depth. A gas limit 4x larger would have opened up the attack.
   210  *   The multisig actor was received by the actors team (via spec) as more or less complete, with change discouraged. Apart from adding tests, we did not spend a lot of time with it.
   211  *   The state transaction mechanism inherited from the spec was intended to prevent this general class of issue, but it did not because the multisig does multiple transactions in a single method.
   212  *   The Checks-Effects-Interactions pattern would be an effective defence against this issue.
   213      *   It’s difficult to fully comply across the whole codebase
   214  *   Unit tests are not sufficient to exercise recursive methods
   215      *   We didn’t have the VM at the time we tested this actor.
   216      *   If we had used VM tests, very likely the test author would have considered recursion.
   217  *   None of the actors team were experienced smart contract developers at the time
   218  *   **Recommendation**: implement tests for actors that essentially interact with others as VM tests, exercising the complete interaction
   219  *   **Recommendation**: implement agent-based testing to range over possible actor behaviour, including actors like the multisig and payment channel
   220  
   221  
   222  # Summary discussion and recommendations
   223  <a name="recc"></a>
   224  
   225  
   226  *   Golden files are a useful tool for avoiding unintended behavior changes. Generating golden files from test traces and always running tests against these traces.
   227  *   Non-determinism needs to be taken seriously because this behavior is hard to detect. This makes errors more likely to reach mainnet, where they always have a high negative impact.
   228  *   For consensus fault and multisig, interactions with the runtime were a significant factor. It is not always clear that tests are missing coverage on code paths that rely on real runtime behavior. The mock runtime can hide things.
   229  *   “More testing” can’t be the only solution: the miner actor received a lot of testing attention and coverage and was the site of the two most serious bugs
   230  *   A more clear and precise spec that implementations adhered to more strictly would have helped with consensus fault, and possibly multisig too. For example, this would have helped us launch mainnet with a stack depth limit.
   231  *   Behaviour automation with agents could cover a lot of ground that we won’t reach with manual testing. That approach could have found the multisig reentrancy as well as non-determinism (coupled with golden file tracing).
   232  *   None of these errors could be detected by line coverage metrics alone. However some metrics about path coverage might give insights into less exercised actor code. We can get path/value coverage with behaviour automation (agent tests)
   233  *   **Recommendation**: move multisig tests to scenario tests using the testing VM, rely more heavily on the testing VM for new tests.
   234  *   **Recommendation**: more tracing/metrics about coverage of edge cases, not line coverage but, e.g. parameter coverage over methods. For example we should be able to know right away that we test sector termination in a single partition but not in multiple.
   235  *   **Recommendation**: just revising all our tests is possibly a way to draw out some new things. There’s likely cruft that, if cleaned out, could reveal new testing angles.
   236  
   237  
   238  ## What *has* been working for us?
   239  <a name="working"></a>
   240  
   241  *   State invariant checks
   242      *   These have detected errors in code that produce unexpected state.
   243  *   Scenario tests, whenever there are interactions between actors.
   244      *   Very useful for reproducing errors.
   245  *   External auditing has caught lots of things.
   246  *   Unit test coverage.
   247  *   Very good for verifying the effects of a change in code.