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

[Bug-Candidate]: Invalid signature returned by _extract_function_relations() #1331

Open
Boyan-MILANOV opened this issue Aug 8, 2022 · 5 comments
Labels
bug Something isn't working echidna

Comments

@Boyan-MILANOV
Copy link
Contributor

Describe the issue:

Trying to get function relations on the contract of the Fallback ethernaut challenge returns a result that contains an invalid function signature: ()

{
  "constructor()": {
    "impacts": [
      "contribute()",
      "getContribution()",
      "withdraw()",
      "()"
    ],
    "is_impacted_by": []
  },
  ...

I think slither returns () instead of receive() for the receive function prototype

Code example to reproduce the issue:

contract Fallback {

  using SafeMath for uint256;
  mapping(address => uint) public contributions;
  address payable public owner;

  constructor() public {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
  }

  modifier onlyOwner {
        require(
            msg.sender == owner,
            "caller is not the owner"
        );
        _;
    }

  function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if(contributions[msg.sender] > contributions[owner]) {
      owner = msg.sender;
    }
  }

  function getContribution() public view returns (uint) {
    return contributions[msg.sender];
  }

  function withdraw() public onlyOwner {
    owner.transfer(address(this).balance);
  }

  receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }
}

Version:

0.8.3

Relevant log output:

No response

@Boyan-MILANOV Boyan-MILANOV added the bug-candidate Bugs reports that are not yet confirmed label Aug 8, 2022
@Boyan-MILANOV Boyan-MILANOV changed the title [Bug-Candidate]: Invalid signature in returned by _extract_function_relations() [Bug-Candidate]: Invalid signature returned by _extract_function_relations() Aug 8, 2022
@plotchy
Copy link
Contributor

plotchy commented Aug 8, 2022

This is used in the echidna printer and relies on the _get_name() helper function.

def _get_name(f: Union[Function, Variable]) -> str:
# Return the name of the function or variable
if isinstance(f, Function):
if f.is_fallback or f.is_receive:
return "()"
return f.solidity_signature
return f.function_name

def _get_name(f: Union[Function, Variable]) -> str:
    # Return the name of the function or variable
    if isinstance(f, Function):
        if f.is_fallback or f.is_receive:
            return "()"
        return f.solidity_signature
    return f.function_name

Can be adjusted to:

def _get_name(f: Union[Function, Variable]) -> str:
    # Return the name of the function or variable
    if isinstance(f, Function):
        if f.is_fallback:
            return "fallback()"
        elif f.is_receive:
            return "receive()"
    return f.solidity_signature

Now provides output:

"constructor()": {
                "impacts": [
                    "contribute()",
                    "getContribution()",
                    "withdraw()",
                    "receive()"
                ],
                "is_impacted_by": []
            },
...
"receive()": {
                "impacts": [
                    "contribute()",
                    "withdraw()"
                ],
                "is_impacted_by": [
                    "constructor()",
                    "contribute()"
                ]
            }

@montyly
Copy link
Member

montyly commented Aug 9, 2022

Hi @plotchy, that's a great point :)

We actually already changed _get_name to use. solidity_signature instead of function_name (with #1323):

def _get_name(f: Union[Function, Variable]) -> str:
# Return the name of the function or variable
if isinstance(f, Function):
if f.is_fallback or f.is_receive:
return "()"
return f.solidity_signature

However I am a bit unsure about returning fallback() or receive() here. A contract can have a function named fallback (same for receive):

contract C{
    function fallback() public{
    }       
    fallback() external{
    }       
}

So it would create collision/confusion.

However maybe we could use a special placeholder for the impact/is_impacted_by list, to indicate if the destination is the receiver/fallback.

@plotchy
Copy link
Contributor

plotchy commented Aug 9, 2022

Ah, @montyly youre totally right. I like the idea of using placeholders. I briefly tested with *receive() and *fallback(), but I seem to be getting collision issues within the Slither detector.

Assuming the list is getting deduplicated on names or something.

contract Fallback {
  function fallback() public payable {}
  function receive() public payable {}

  receive() external payable {}
  fallback() external payable {}
}
{
    "payable": {
        "Fallback": [
            "*fallback()",  
            "*receive()"   < -- "fallback()" and "receive()" Not recognized here
        ]
    },
    "functions_relations": {
        "Fallback": {
            "*fallback()": {
                "impacts": [],
                "is_impacted_by": []
            },
            "*receive()": {
                "impacts": [],
                "is_impacted_by": []
            }
        }
    },
    "constructors": {},
    "have_external_calls": {},
    "call_a_parameter": {},
    "use_balance": {},
    "solc_versions": [
        "0.8.13"
    ],
    "with_fallback": [ <-- Are recognized here
        "Fallback"
    ],
    "with_receive": [  <-- Are recognized here
        "Fallback"
    ]
}

Even if the functions impact each other and should all be in the impact_list, only the *receive() or *fallback() were listed.

@plotchy
Copy link
Contributor

plotchy commented Aug 9, 2022

@0xalpharush Hmm after looking at that line it seems ambiguous how functions_entry_points is designed with regards to fallback. IIRC Python has higher precedence on and than or. I think the below is the way it runs:
if (f.visibility in ["public", "external"] and not f.is_shadowed) or f.is_fallback . Seems that it should return fallback and receive as they are entry points.

Below is a minimal way to test impacted_list. Commenting out the fallback() external payable or receive() external payable will adjust how the impacted lists react.

contract Fallback {
    mapping(address => uint) public contributions;
    address payable public owner;

  constructor() public {
    owner = payable(msg.sender);
    contributions[msg.sender] = 1000 * (1 ether);
  }

  function fallback() public payable {
    contributions[msg.sender] += msg.value;
  }

  function receive() public payable {
    contributions[msg.sender] += msg.value;
  }

  receive() external payable {
    contributions[msg.sender] += msg.value;
  }

  fallback() external payable {
    contributions[msg.sender] += msg.value;
  }
}

@0xalpharush
Copy link
Contributor

0xalpharush commented Aug 9, 2022

You're right that it's not the source of the issue. At a glance, I suspect the canonical_name might be identical and messing up the contract parsing in solc_parsing/.

@0xalpharush 0xalpharush added bug Something isn't working and removed bug-candidate Bugs reports that are not yet confirmed labels Aug 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working echidna
Projects
None yet
Development

No branches or pull requests

4 participants