medium

vyper-serve unable to compile bytecode due to changes in vyper_compile.py's c...

Reward

Total

10167.11 USDC

Selected
10167.11 USDC
Selected Submission

vyper-serve unable to compile bytecode due to changes in vyper_compile.py's compile_files function definition

Severity

Medium Risk

Relevant GitHub Links

https://github.com/vyperlang/vyper/blob/0b740280c1e3c5528a20d47b29831948ddcc6d83/vyper/cli/vyper_compile.py#L279-L287

https://github.com/vyperlang/vyper/blob/0b740280c1e3c5528a20d47b29831948ddcc6d83/vyper/cli/vyper_serve.py#L85-L97

Summary

In vyper's version 0.3.10 vyper-serve is unable to compile bytecode HTTP requests due to changes made on cli/vyper_compile.py's compile_files function parameters definitions as shown below.
This made unable to use vyper-serve to compile contracts via HTTP

Vulnerability Details

On vyper's 0.3.9 version the cli/vyper_compile.py's compiles_files function declaration is the following:

# cli/vyper_compile.py version 0.3.9
def compile_files(
    input_files: Iterable[str],
    output_formats: OutputFormats,
    root_folder: str = ".",
    show_gas_estimates: bool = False,
    evm_version: str = DEFAULT_EVM_VERSION,
    no_optimize: bool = False,
    storage_layout: Iterable[str] = None,
    no_bytecode_metadata: bool = False,
) -> OrderedDict:
	# ...

However, vyper's cli/vyper_compile.py's compile_files 0.3.10rc3 version removes EVM_VERSION_FIELD paramater:

# cli/vyper_compile.py version 0.3.10rc3  
def compile_files(
    input_files: Iterable[str],
    output_formats: OutputFormats,
    root_folder: str = ".",
    show_gas_estimates: bool = False,
    settings: Optional[Settings] = None,
    storage_layout: Optional[Iterable[str]] = None,
    no_bytecode_metadata: bool = False,
) -> OrderedDict:
	# ... 

compile_files is called on vyper_serve.py with evm_version as an argument:

# vyper_serve.py
def _compile(self, data):
        code = data.get("code")
        # ... 
        try:
            code = data["code"]
            out_dict = vyper.compile_codes(
                {"": code},
                list(vyper.compiler.OUTPUT_FORMATS.keys()),
                evm_version=data.get("evm_version", DEFAULT_EVM_VERSION),	# <<== EVM_VERSION 
            )[""]

So, now the arguments passed to cli/vyper_compile.py aren't valid and vyper_serve.py is not able to produce bytecode anymore.

An easy way to comprobe this vulnerability is installing both versions (0.3.9 and 0.3.10rc3) as shown next:

  • Step 1 Install 0.3.9 and launch vyper-serve:
    In one terminal install 0.3.9 version:
cd /tmp/
virtualenv vyper_venv9
source vyper_venv9/bin/activate
pip install vyper==0.3.9
vyper --version

Start vyper-serve:

vyper-serve

Using an http request compile a contract:

curl -X POST localhost:8000/compile -H "Content-Type: application/json" -d '{"code": "\n\n# @version ^0.3.7\n\n@external\ndef foo():\n    pass\n"}'

Observe the successful response:

{
  "ast_dict": {
    "contract_name": "",
    "ast": {
      "ast_type": "Module",
      "src": "0:50:0",
      "end_col_offset": 8,
      "doc_string": null,
      "node_id": 0,
      "lineno": 1,
      "body": [
        {
          "args": {
            "args": ...
		}
	...
}

Stop the vyper-serve using Ctrl-C

  • Step 2 Observe vyper-serve 0.3.10 is unable to compile bytecode
    Install 0.3.10 version:
cd /tmp/
virtualenv vyper_venv10
source vyper_venv10/bin/activate
pip install vyper==0.3.10rc3
vyper --version

Start vyper-serve:

vyper-serve

Using an http request try to compile the same contract:

curl -X POST localhost:8000/compile -H "Content-Type: application/json" -d '{"code": "\n\n# @version ^0.3.7\n\n@external\ndef foo():\n    pass\n"}'

Observe the response:

curl: (52) Empty reply from server

And the stack trace from vyper-serve console:

----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 44642)
Traceback (most recent call last):
  File "/usr/lib/python3.10/socketserver.py", line 683, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.10/socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.10/socketserver.py", line 747, in __init__
    self.handle()
  File "/usr/lib/python3.10/http/server.py", line 433, in handle
    self.handle_one_request()
  File "/usr/lib/python3.10/http/server.py", line 421, in handle_one_request
    method()
  File "/tmp/vyper_venv10/lib/python3.10/site-packages/vyper/cli/vyper_serve.py", line 72, in do_POST
    response, status_code = self._compile(data)
  File "/tmp/vyper_venv10/lib/python3.10/site-packages/vyper/cli/vyper_serve.py", line 94, in _compile
    out_dict = vyper.compile_codes(
TypeError: compile_codes() got an unexpected keyword argument 'evm_version'
----------------------------------------

This is due to the change made on compile_files explained above.

Impact

Users can't use vyper-serve to compile bytecode leading to a loss of functionality / denial of service

Impact: Low
Likehood: High

CVSS Medium - 4.3
AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L

Tools Used

Manual analysis

Recommended Mitigation

Change cli/vyper_compile.py compile_files function definition to take into account the evm_version argument from vyper_serve.py@_compile function (for example in version 0.3.9)