• YouTube Channel
  • System Status
  • VS Code Extension
  • Shadowing Builtin Name

    Overview

    What is the Shadowing Builtin vulnerability?

    Solidity provides a number of special variables and functions as part of the language which can be used in smart contracts. In general, the compiler will not prevent the developer from shadowing these names with their own declarations; however, in doing so, they may inadvertently cause errors in their smart contract if they expect to use a builtin symbol that has been shadowed.

    Further reading: Solidity Documentation: Special Variables and Functions

    Technical example of vulnerable code

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      contract MessageHandler {
          // Custom struct to hold in-protocol messages
          struct Message {
              address sender;
              address to;
              string content;
          }
    
          // Store messages handled by the contract
          mapping(uint => Message) public allMessages;
          uint public messageCount;
    
          // Also cache latest messages created by each user
          mapping(address => Message) public lastCreatedBy;
    
          // Function to create a new message
          function createMessage(address _from, address _to, string memory _content) public {
              Message memory msg = Message({
                  sender: _from,
                  to: _to,
                  content: _content
              });
    
              // Developer intends to access the global 'msg.sender' but accidentally shadows it with 'msg' struct
              lastCreatedBy[msg.sender] = msg;
    
              allMessages[messageCount] = msg;
              messageCount++;
          }
    
          // Function to retrieve the sender of the last message
          function getLastMessageSender() public view returns (address) {
              require(messageCount > 0, "No messages exist.");
              return allMessages[messageCount - 1].sender;
          }
    
          // Additional functionality omitted for brevity
      }
    
    

    In the example above, contract MessageHandler implements some basic logic for handling and storing custom messages; depending on the content of the messages these could potentially be related to some kind of decentralized finance (DeFi) protocol, though for demonstration purposes they are left generic. In the function createMessage(), a new instance of a custom Message struct is created with the name msg, which is a builtin name in Solidity. When the developer subsequently attempts to access the msg.sender property of the builtin variable, they will instead access the value stored in the struct, which could be different and might lead to subtle logical errors in a protocol using these messages.

    Technical example of how to fix the vulnerability

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      contract MessageHandlerUpdated {
          // Custom struct to hold in-protocol messages
          struct Message {
              address sender;
              address to;
              string content;
          }
    
          // Store messages handled by the contract
          mapping(uint => Message) public allMessages;
          uint public messageCount;
    
          // Also cache latest messages created by each user
          mapping(address => Message) public lastCreatedBy;
    
          // Function to create a new message
          function createMessage(address _from, address _to, string memory _content) public {
              Message memory newMsg = Message({
                  sender: _from,
                  to: _to,
                  content: _content
              });
    
              // The msg symbol is no longer shadowed, so this works as intended
              lastCreatedBy[msg.sender] = newMsg;
    
              allMessages[messageCount] = newMsg;
              messageCount++;
          }
    
          // Function to retrieve the sender of the last message
          function getLastMessageSender() public view returns (address) {
              require(messageCount > 0, "No messages exist.");
              return allMessages[messageCount - 1].sender;
          }
    
          // Additional functionality omitted for brevity
      }
    
    

    In the revised example, contract MessageHandlerUpdated now uses a local struct variable named newMsg, avoiding the shadowing of the builtin that they wish to access for updating the lastCreatedBy mapping.