Update security-considerations.rst (#14079)

* Update security-considerations.rst

Spell checking mostly

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* fixup! Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update docs/security-considerations.rst

* Update security-considerations.rst

* Update security-considerations.rst

* Update docs/security-considerations.rst

Co-authored-by: r0qs <deepmarolaest@gmail.com>

---------

Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
Co-authored-by: r0qs <deepmarolaest@gmail.com>
This commit is contained in:
Nuno Santos 2023-05-02 17:05:23 +01:00 committed by GitHub
parent 0cb279494a
commit 49cf49184a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -7,28 +7,28 @@ Security Considerations
While it is usually quite easy to build software that works as expected, While it is usually quite easy to build software that works as expected,
it is much harder to check that nobody can use it in a way that was **not** anticipated. it is much harder to check that nobody can use it in a way that was **not** anticipated.
In Solidity, this is even more important because you can use smart contracts In Solidity, this is even more important because you can use smart contracts to handle tokens or,
to handle tokens or, possibly, even more valuable things. Furthermore, every possibly, even more valuable things.
execution of a smart contract happens in public and, in addition to that, Furthermore, every execution of a smart contract happens in public and,
the source code is often available. in addition to that, the source code is often available.
Of course you always have to consider how much is at stake: Of course, you always have to consider how much is at stake:
You can compare a smart contract with a web service that is open to the You can compare a smart contract with a web service that is open to the public
public (and thus, also to malicious actors) and perhaps even open source. (and thus, also to malicious actors) and perhaps even open-source.
If you only store your grocery list on that web service, you might not have If you only store your grocery list on that web service, you might not have to take too much care,
to take too much care, but if you manage your bank account using that web service, but if you manage your bank account using that web service, you should be more careful.
you should be more careful.
This section will list some pitfalls and general security recommendations but This section will list some pitfalls and general security recommendations
can, of course, never be complete. Also, keep in mind that even if your smart but can, of course, never be complete.
contract code is bug-free, the compiler or the platform itself might have a Also, keep in mind that even if your smart contract code is bug-free,
bug. A list of some publicly known security-relevant bugs of the compiler can the compiler or the platform itself might have a bug.
be found in the :ref:`list of known bugs<known_bugs>`, which is also A list of some publicly known security-relevant bugs of the compiler can be found
machine-readable. Note that there is a bug bounty program that covers the code in the :ref:`list of known bugs<known_bugs>`, which is also machine-readable.
generator of the Solidity compiler. Note that there is a `Bug Bounty Program <https://ethereum.org/en/bug-bounty/>`_
that covers the code generator of the Solidity compiler.
As always, with open source documentation, please help us extend this section As always, with open-source documentation,
(especially, some examples would not hurt)! please help us extend this section (especially, some examples would not hurt)!
NOTE: In addition to the list below, you can find more security recommendations and best practices NOTE: In addition to the list below, you can find more security recommendations and best practices
`in Guy Lando's knowledge list <https://github.com/guylando/KnowledgeLists/blob/master/EthereumSmartContracts.md>`_ and `in Guy Lando's knowledge list <https://github.com/guylando/KnowledgeLists/blob/master/EthereumSmartContracts.md>`_ and
@ -41,20 +41,18 @@ Pitfalls
Private Information and Randomness Private Information and Randomness
================================== ==================================
Everything you use in a smart contract is publicly visible, even Everything you use in a smart contract is publicly visible,
local variables and state variables marked ``private``. even local variables and state variables marked ``private``.
Using random numbers in smart contracts is quite tricky if you do not want Using random numbers in smart contracts is quite tricky if you do not want block builders to be able to cheat.
block builders to be able to cheat.
Reentrancy Reentrancy
=========== ==========
Any interaction from a contract (A) with another contract (B) and any transfer Any interaction from a contract (A) with another contract (B)
of Ether hands over control to that contract (B). This makes it possible for B and any transfer of Ether hands over control to that contract (B).
to call back into A before this interaction is completed. To give an example, This makes it possible for B to call back into A before this interaction is completed.
the following code contains a bug (it is just a snippet and not a To give an example, the following code contains a bug (it is just a snippet and not a complete contract):
complete contract):
.. code-block:: solidity .. code-block:: solidity
@ -72,12 +70,12 @@ complete contract):
} }
} }
The problem is not too serious here because of the limited gas as part The problem is not too serious here because of the limited gas as part of ``send``,
of ``send``, but it still exposes a weakness: Ether transfer can always but it still exposes a weakness:
include code execution, so the recipient could be a contract that calls Ether transfer can always include code execution,
back into ``withdraw``. This would let it get multiple refunds and so the recipient could be a contract that calls back into ``withdraw``.
basically retrieve all the Ether in the contract. In particular, the This would let it get multiple refunds and, basically, retrieve all the Ether in the contract.
following contract will allow an attacker to refund multiple times In particular, the following contract will allow an attacker to refund multiple times
as it uses ``call`` which forwards all remaining gas by default: as it uses ``call`` which forwards all remaining gas by default:
.. code-block:: solidity .. code-block:: solidity
@ -97,8 +95,7 @@ as it uses ``call`` which forwards all remaining gas by default:
} }
} }
To avoid reentrancy, you can use the Checks-Effects-Interactions pattern as To avoid reentrancy, you can use the Checks-Effects-Interactions pattern as demonstrated below:
demonstrated below:
.. code-block:: solidity .. code-block:: solidity
@ -116,58 +113,58 @@ demonstrated below:
} }
} }
The Checks-Effects-Interactions pattern ensures that all code paths through a contract complete all required checks The Checks-Effects-Interactions pattern ensures that all code paths through a contract
of the supplied parameters before modifying the contract's state (Checks); only then it makes any changes to the state (Effects); complete all required checks of the supplied parameters before modifying the contract's state (Checks);
it may make calls to functions in other contracts *after* all planned state changes have been written to only then it makes any changes to the state (Effects);
storage (Interactions). This is a common foolproof way to prevent *reentrancy attacks*, where an externally called it may make calls to functions in other contracts
malicious contract is able to double-spend an allowance, double-withdraw a balance, among other things, by using logic that calls back into the *after* all planned state changes have been written to storage (Interactions).
original contract before it has finalized its transaction. This is a common foolproof way to prevent *reentrancy attacks*,
where an externally called malicious contract can double-spend an allowance,
double-withdraw a balance, among other things,
by using logic that calls back into the original contract before it has finalized its transaction.
Note that reentrancy is not only an effect of Ether transfer but of any Note that reentrancy is not only an effect of Ether transfer
function call on another contract. Furthermore, you also have to take but of any function call on another contract.
multi-contract situations into account. A called contract could modify the Furthermore, you also have to take multi-contract situations into account.
state of another contract you depend on. A called contract could modify the state of another contract you depend on.
Gas Limit and Loops Gas Limit and Loops
=================== ===================
Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Loops that do not have a fixed number of iterations, for example,
Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to loops that depend on storage values, have to be used carefully:
normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete Due to the block gas limit, transactions can only consume a certain amount of gas.
contract to be stalled at a certain point. This may not apply to ``view`` functions that are only executed Either explicitly or just due to normal operation,
to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations the number of iterations in a loop can grow beyond the block gas limit
and stall those. Please be explicit about such cases in the documentation of your contracts. which can cause the complete contract to be stalled at a certain point.
This may not apply to ``view`` functions that are only executed to read data from the blockchain.
Still, such functions may be called by other contracts as part of on-chain operations and stall those.
Please be explicit about such cases in the documentation of your contracts.
Sending and Receiving Ether Sending and Receiving Ether
=========================== ===========================
- Neither contracts nor "external accounts" are currently able to prevent that someone sends them Ether. - Neither contracts nor "external accounts" are currently able to prevent someone from sending them Ether.
Contracts can react on and reject a regular transfer, but there are ways Contracts can react on and reject a regular transfer, but there are ways to move Ether without creating a message call.
to move Ether without creating a message call. One way is to simply "mine to" One way is to simply "mine to" the contract address and the second way is using ``selfdestruct(x)``.
the contract address and the second way is using ``selfdestruct(x)``.
- If a contract receives Ether (without a function being called), - If a contract receives Ether (without a function being called), either the :ref:`receive Ether <receive-ether-function>`
either the :ref:`receive Ether <receive-ether-function>`
or the :ref:`fallback <fallback-function>` function is executed. or the :ref:`fallback <fallback-function>` function is executed.
If it does not have a receive nor a fallback function, the Ether will be If it does not have a ``receive`` nor a ``fallback`` function, the Ether will be rejected (by throwing an exception).
rejected (by throwing an exception). During the execution of one of these During the execution of one of these functions, the contract can only rely on the "gas stipend" it is passed (2300 gas)
functions, the contract can only rely on the "gas stipend" it is passed (2300 being available to it at that time.
gas) being available to it at that time. This stipend is not enough to modify This stipend is not enough to modify storage (do not take this for granted though, the stipend might change with future hard forks).
storage (do not take this for granted though, the stipend might change with To be sure that your contract can receive Ether in that way, check the gas requirements of the receive and fallback functions
future hard forks). To be sure that your contract can receive Ether in that
way, check the gas requirements of the receive and fallback functions
(for example in the "details" section in Remix). (for example in the "details" section in Remix).
- There is a way to forward more gas to the receiving contract using - There is a way to forward more gas to the receiving contract using ``addr.call{value: x}("")``.
``addr.call{value: x}("")``. This is essentially the same as ``addr.transfer(x)``, This is essentially the same as ``addr.transfer(x)``, only that it forwards all remaining gas
only that it forwards all remaining gas and opens up the ability for the and opens up the ability for the recipient to perform more expensive actions
recipient to perform more expensive actions (and it returns a failure code (and it returns a failure code instead of automatically propagating the error).
instead of automatically propagating the error). This might include calling back This might include calling back into the sending contract or other state changes you might not have thought of.
into the sending contract or other state changes you might not have thought of.
So it allows for great flexibility for honest users but also for malicious actors. So it allows for great flexibility for honest users but also for malicious actors.
- Use the most precise units to represent the wei amount as possible, as you lose - Use the most precise units to represent the Wei amount as possible, as you lose any that is rounded due to a lack of precision.
any that is rounded due to a lack of precision.
- If you want to send Ether using ``address.transfer``, there are certain details to be aware of: - If you want to send Ether using ``address.transfer``, there are certain details to be aware of:
@ -191,24 +188,28 @@ Sending and Receiving Ether
Call Stack Depth Call Stack Depth
================ ================
External function calls can fail any time because they exceed the maximum External function calls can fail at any time
call stack size limit of 1024. In such situations, Solidity throws an exception. because they exceed the maximum call stack size limit of 1024.
In such situations, Solidity throws an exception.
Malicious actors might be able to force the call stack to a high value Malicious actors might be able to force the call stack to a high value
before they interact with your contract. Note that, since `Tangerine Whistle <https://eips.ethereum.org/EIPS/eip-608>`_ hardfork, the `63/64 rule <https://eips.ethereum.org/EIPS/eip-150>`_ makes call stack depth attack impractical. Also note that the call stack and the expression stack are unrelated, even though both have a size limit of 1024 stack slots. before they interact with your contract.
Note that, since `Tangerine Whistle <https://eips.ethereum.org/EIPS/eip-608>`_ hardfork,
the `63/64 rule <https://eips.ethereum.org/EIPS/eip-150>`_ makes call stack depth attack impractical.
Also note that the call stack and the expression stack are unrelated,
even though both have a size limit of 1024 stack slots.
Note that ``.send()`` does **not** throw an exception if the call stack is Note that ``.send()`` does **not** throw an exception if the call stack is depleted
depleted but rather returns ``false`` in that case. The low-level functions but rather returns ``false`` in that case.
``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way. The low-level functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way.
Authorized Proxies Authorized Proxies
================== ==================
If your contract can act as a proxy, i.e. if it can call arbitrary contracts If your contract can act as a proxy, i.e. if it can call arbitrary contracts with user-supplied data,
with user-supplied data, then the user can essentially assume the identity then the user can essentially assume the identity of the proxy contract.
of the proxy contract. Even if you have other protective measures in place, Even if you have other protective measures in place, it is best to build your contract system such
it is best to build your contract system such that the proxy does not have that the proxy does not have any permissions (not even for itself).
any permissions (not even for itself). If needed, you can accomplish that If needed, you can accomplish that using a second proxy:
using a second proxy:
.. code-block:: solidity .. code-block:: solidity
@ -236,7 +237,8 @@ using a second proxy:
tx.origin tx.origin
========= =========
Never use tx.origin for authorization. Let's say you have a wallet contract like this: Never use ``tx.origin`` for authorization.
Let's say you have a wallet contract like this:
.. code-block:: solidity .. code-block:: solidity
@ -279,7 +281,11 @@ Now someone tricks you into sending Ether to the address of this attack wallet:
} }
} }
If your wallet had checked ``msg.sender`` for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking ``tx.origin``, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds. If your wallet had checked ``msg.sender`` for authorization, it would get the address of the attack wallet,
instead of the owner's address.
But by checking ``tx.origin``, it gets the original address that kicked off the transaction,
which is still the owner's address.
The attack wallet instantly drains all your funds.
.. _underflow-overflow: .. _underflow-overflow:
@ -319,16 +325,14 @@ Try to use ``require`` to limit the size of inputs to a reasonable range and use
Clearing Mappings Clearing Mappings
================= =================
The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure
key-value data structure that does not keep track of the keys that were that does not keep track of the keys that were assigned a non-zero value.
assigned a non-zero value. Because of that, cleaning a mapping without extra Because of that, cleaning a mapping without extra information about the written keys is not possible.
information about the written keys is not possible. If a ``mapping`` is used as the base type of a dynamic storage array,
If a ``mapping`` is used as the base type of a dynamic storage array, deleting deleting or popping the array will have no effect over the ``mapping`` elements.
or popping the array will have no effect over the ``mapping`` elements. The The same happens, for example, if a ``mapping`` is used as the type of a member field of a ``struct``
same happens, for example, if a ``mapping`` is used as the type of a member that is the base type of a dynamic storage array.
field of a ``struct`` that is the base type of a dynamic storage array. The The ``mapping`` is also ignored in assignments of structs or arrays containing a ``mapping``.
``mapping`` is also ignored in assignments of structs or arrays containing a
``mapping``.
.. code-block:: solidity .. code-block:: solidity
@ -356,15 +360,12 @@ field of a ``struct`` that is the base type of a dynamic storage array. The
} }
} }
Consider the example above and the following sequence of calls: ``allocate(10)``, Consider the example above and the following sequence of calls: ``allocate(10)``, ``writeMap(4, 128, 256)``.
``writeMap(4, 128, 256)``.
At this point, calling ``readMap(4, 128)`` returns 256. At this point, calling ``readMap(4, 128)`` returns 256.
If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but If we call ``eraseMaps``, the length of the state variable ``array`` is zeroed,
since its ``mapping`` elements cannot be zeroed, their information stays alive but since its ``mapping`` elements cannot be zeroed, their information stays alive in the contract's storage.
in the contract's storage. After deleting ``array``, calling ``allocate(5)`` allows us to access ``array[4]`` again,
After deleting ``array``, calling ``allocate(5)`` allows us to access and calling ``readMap(4, 128)`` returns 256 even without another call to ``writeMap``.
``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without
another call to ``writeMap``.
If your ``mapping`` information must be deleted, consider using a library similar to If your ``mapping`` information must be deleted, consider using a library similar to
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_, `iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
@ -375,10 +376,11 @@ Minor Details
- Types that do not occupy the full 32 bytes might contain "dirty higher order bits". - Types that do not occupy the full 32 bytes might contain "dirty higher order bits".
This is especially important if you access ``msg.data`` - it poses a malleability risk: This is especially important if you access ``msg.data`` - it poses a malleability risk:
You can craft transactions that call a function ``f(uint8 x)`` with a raw byte argument You can craft transactions that call a function ``f(uint8 x)``
of ``0xff000001`` and with ``0x00000001``. Both are fed to the contract and both will with a raw byte argument of ``0xff000001`` and with ``0x00000001``.
look like the number ``1`` as far as ``x`` is concerned, but ``msg.data`` will Both are fed to the contract and both will look like the number ``1`` as far as ``x`` is concerned,
be different, so if you use ``keccak256(msg.data)`` for anything, you will get different results. but ``msg.data`` will be different, so if you use ``keccak256(msg.data)`` for anything,
you will get different results.
*************** ***************
Recommendations Recommendations
@ -388,48 +390,45 @@ Take Warnings Seriously
======================= =======================
If the compiler warns you about something, you should change it. If the compiler warns you about something, you should change it.
Even if you do not think that this particular warning has security Even if you do not think that this particular warning has security implications,
implications, there might be another issue buried beneath it. there might be another issue buried beneath it.
Any compiler warning we issue can be silenced by slight changes to the Any compiler warning we issue can be silenced by slight changes to the code.
code.
Always use the latest version of the compiler to be notified about all recently Always use the latest version of the compiler to be notified about all recently introduced warnings.
introduced warnings.
Messages of type ``info`` issued by the compiler are not dangerous, and simply Messages of type ``info``, issued by the compiler, are not dangerous
represent extra suggestions and optional information that the compiler thinks and simply represent extra suggestions and optional information
might be useful to the user. that the compiler thinks might be useful to the user.
Restrict the Amount of Ether Restrict the Amount of Ether
============================ ============================
Restrict the amount of Ether (or other tokens) that can be stored in a smart Restrict the amount of Ether (or other tokens) that can be stored in a smart contract.
contract. If your source code, the compiler or the platform has a bug, these If your source code, the compiler or the platform has a bug, these funds may be lost.
funds may be lost. If you want to limit your loss, limit the amount of Ether. If you want to limit your loss, limit the amount of Ether.
Keep it Small and Modular Keep it Small and Modular
========================= =========================
Keep your contracts small and easily understandable. Single out unrelated Keep your contracts small and easily understandable.
functionality in other contracts or into libraries. General recommendations Single out unrelated functionality in other contracts or into libraries.
about source code quality of course apply: Limit the amount of local variables, General recommendations about the source code quality of course apply:
the length of functions and so on. Document your functions so that others Limit the amount of local variables, the length of functions and so on.
can see what your intention was and whether it is different than what the code does. Document your functions so that others can see what your intention was
and whether it is different than what the code does.
Use the Checks-Effects-Interactions Pattern Use the Checks-Effects-Interactions Pattern
=========================================== ===========================================
Most functions will first perform some checks (who called the function, Most functions will first perform some checks and they should be done first
are the arguments in range, did they send enough Ether, does the person (who called the function, are the arguments in range, did they send enough Ether,
have tokens, etc.). These checks should be done first. does the person have tokens, etc.).
As the second step, if all checks passed, effects to the state variables As the second step, if all checks passed, effects to the state variables of the current contract should be made.
of the current contract should be made. Interaction with other contracts Interaction with other contracts should be the very last step in any function.
should be the very last step in any function.
Early contracts delayed some effects and waited for external function Early contracts delayed some effects and waited for external function calls to return in a non-error state.
calls to return in a non-error state. This is often a serious mistake This is often a serious mistake because of the reentrancy problem explained above.
because of the reentrancy problem explained above.
Note that, also, calls to known contracts might in turn cause calls to Note that, also, calls to known contracts might in turn cause calls to
unknown contracts, so it is probably better to just always apply this pattern. unknown contracts, so it is probably better to just always apply this pattern.
@ -437,24 +436,23 @@ unknown contracts, so it is probably better to just always apply this pattern.
Include a Fail-Safe Mode Include a Fail-Safe Mode
======================== ========================
While making your system fully decentralised will remove any intermediary, While making your system fully decentralized will remove any intermediary,
it might be a good idea, especially for new code, to include some kind it might be a good idea, especially for new code, to include some kind of fail-safe mechanism:
of fail-safe mechanism:
You can add a function in your smart contract that performs some You can add a function in your smart contract that performs some self-checks like "Has any Ether leaked?",
self-checks like "Has any Ether leaked?",
"Is the sum of the tokens equal to the balance of the contract?" or similar things. "Is the sum of the tokens equal to the balance of the contract?" or similar things.
Keep in mind that you cannot use too much gas for that, so help through off-chain Keep in mind that you cannot use too much gas for that,
computations might be needed there. so help through off-chain computations might be needed there.
If the self-check fails, the contract automatically switches into some kind If the self-check fails, the contract automatically switches into some kind of "failsafe" mode,
of "failsafe" mode, which, for example, disables most of the features, hands over which, for example, disables most of the features,
control to a fixed and trusted third party or just converts the contract into hands over control to a fixed and trusted third party
a simple "give me back my money" contract. or just converts the contract into a simple "give me back my money" contract.
Ask for Peer Review Ask for Peer Review
=================== ===================
The more people examine a piece of code, the more issues are found. The more people examine a piece of code, the more issues are found.
Asking people to review your code also helps as a cross-check to find out whether your code Asking people to review your code also helps as a cross-check to find out
is easy to understand - a very important criterion for good smart contracts. whether your code is easy to understand -
a very important criterion for good smart contracts.