Hi,
There is a stack overflow bug in json parser when parsing nesting objects.
There is a function named check_string_depth to handle such situation, it tries to make sure the nesting objects' depth is less than 100.
However the check is vulnerable and we can bypass it.
How to test:
1.Start nodeos
2.Execute:
python post.py
to send malicious json rpc request.
3.Observe the crash
This is a submission to EOS bug bounty program and the bug credits to: Yuki Chen of Qihoo 360 Vulcan Team
json stack overflow.zip
I've reproduced it, looks like it's a stack overflow bug:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeef3ffff8)
frame #0: 0x00007fff68a0b22c libsystem_malloc.dylib`default_zone_malloc + 4
libsystem_malloc.dylib`default_zone_malloc:
-> 0x7fff68a0b22c <+4>: pushq %rbx
0x7fff68a0b22d <+5>: pushq %rax
0x7fff68a0b22e <+6>: movq %rsi, %rbx
0x7fff68a0b231 <+9>: movq 0x39478e68(%rip), %rdi ; lite_zone
Target 0: (nodeos) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeef3ffff8)
* frame #0: 0x00007fff68a0b22c libsystem_malloc.dylib`default_zone_malloc + 4
frame #1: 0x00007fff689fb201 libsystem_malloc.dylib`malloc_zone_malloc + 103
frame #2: 0x00007fff689fa50b libsystem_malloc.dylib`malloc + 24
frame #3: 0x00000001029161b8 libc++.1.dylib`operator new(unsigned long) + 40
frame #4: 0x00000001006d1303 nodeos`fc::mutable_variant_object::mutable_variant_object() + 19
frame #5: 0x00000001006f2754 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 52
frame #6: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #7: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #8: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #9: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #10: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #11: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #12: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #13: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #14: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #15: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #16: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #17: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #18: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #19: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #20: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #21: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #22: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #23: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #24: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #25: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #26: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #27: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #28: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #29: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #30: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #31: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #32: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #33: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #34: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #35: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #36: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #37: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #38: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #39: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #40: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #41: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
frame #42: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #43: 0x00000001006f2884 nodeos`fc::variant_object fc::objectFromStream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 356
...
... (repeat many many times)...
...
...
frame #10804: 0x00000001006e7f66 nodeos`fc::variant fc::variant_from_stream<std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >, (fc::json::parse_type)0>(std::__1::basic_stringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 390
frame #10805: 0x00000001006e6f35 nodeos`fc::json::from_string(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, fc::json::parse_type) + 309
frame #10806: 0x00000001001989d0 nodeos`std::__1::__function::__func<eosio::chain_api_plugin::plugin_startup()::$_0, std::__1::allocator<eosio::chain_api_plugin::plugin_startup()::$_0>, void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::function<void (int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)>)>::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&&, std::__1::function<void (int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)>&&) + 240
frame #10807: 0x00000001002da204 nodeos`void eosio::http_plugin_impl::handle_http_request<websocketpp::transport::asio::basic_socket::endpoint>(websocketpp::server<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >::connection_ptr) + 1076
frame #10808: 0x00000001002d9d77 nodeos`void eosio::http_plugin_impl::create_server_for_endpoint<websocketpp::transport::asio::basic_socket::endpoint>(boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> const&, websocketpp::server<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >&)::'lambda'(std::__1::weak_ptr<void>)::operator()(std::__1::weak_ptr<void>) const + 71
frame #10809: 0x00000001002d9ccc nodeos`std::__1::__function::__func<void eosio::http_plugin_impl::create_server_for_endpoint<websocketpp::transport::asio::basic_socket::endpoint>(boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> const&, websocketpp::server<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >&)::'lambda'(std::__1::weak_ptr<void>), std::__1::allocator<void eosio::http_plugin_impl::create_server_for_endpoint<websocketpp::transport::asio::basic_socket::endpoint>(boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> const&, websocketpp::server<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >&)::'lambda'(std::__1::weak_ptr<void>)>, void (std::__1::weak_ptr<void>)>::operator()(std::__1::weak_ptr<void>&&) + 44
frame #10810: 0x00000001002fe399 nodeos`websocketpp::connection<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >::process_handshake_request() + 521
frame #10811: 0x00000001002fcfd8 nodeos`websocketpp::connection<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint> >::handle_read_handshake(std::__1::error_code const&, unsigned long) + 1144
frame #10812: 0x00000001002ed43c nodeos`websocketpp::transport::asio::connection<eosio::detail::asio_with_stub_log<websocketpp::transport::asio::basic_socket::endpoint>::transport_config>::handle_async_read(std::__1::function<void (std::__1::error_code const&, unsigned long)>, boost::system::error_code const&, unsigned long) + 652
Just FYI this is an old bug which has been reported to EOS team one month before. Also commented here https://github.com/EOSIO/eos/commit/a5f1a208acc3dcb926c28697cf3be316f21ab7e7#commitcomment-29160834
Related fix in BitShares:
If there is a bounty for this, it should belong to BitShares Dev Team.
check_string_depth implementation is naive, best approach is to check object tree depth while parsing.
@abitmore thanks for bringing this to our attention, you are correct, we did receive a report of this issue from the BitShares Dev team about a month ago, but we failed to act then and lost track of it (we didn't create a GitHub issue 🤦♂️) so the vulnerability was still in our code when 360.cn brought it to our attention therefore they are eligible for the bounty.
I understand this may be frustrating for the BitShares Dev team. Please do continue to report vulnerabilities to us and you are welcome to do so via the https://hackerone.com/eosio bug bounty program.
@gleehokie I think both deserves the bounty, Specially knowing that the bitshares-dev team report the problem without having any financial benefit. Also 360 received a huge backslash for reporting other problem, I think giving the bounty to both parties would be a responsable community oriented approach that would encourage more contributions.
Most helpful comment
This is a submission to EOS bug bounty program and the bug credits to: Yuki Chen of Qihoo 360 Vulcan Team
json stack overflow.zip