4

Can Rust-wrapped C++ offer stability and performance benefits?

 1 year ago
source link: https://medium.com/@adetaylor/can-rust-wrapped-c-offer-stability-and-performance-benefits-e140b7ca1ba9
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Can Rust-wrapped C++ offer stability and performance benefits?

Crashes are good!

If you’re in a security team, that is. If your program encounters an unexpected condition it should exit, intentionally and swiftly, instead of progressing into an area of undefined behavior (UB) which may be exploited by an attacker.

So Chromium, like many other security-critical open source projects, scatters many CHECK s throughout its source code to look for unexpected conditions. It similarly enables checks in the C++ libraries on which it depends, notably abseil.

Let’s see how some of these checks play out in practice. We’ll use base::Value , the Chromium type which represents trees of arbitrary values (integers, strings, Booleans, nested lists, nested dictionaries, etc.) These trees of values are often used to represent the results of some parsing (for example of a JSON document), and are often passed to a privileged process after some sandboxed parsing.

We’ll use simple examples here, but the code can of course get arbitrarily complex, and the bugs can get arbitrarily hard to avoid.

(You can test these examples by adding them to values_unittest.cc and, with a Chromium build environment on Linux, using the gn args symbol_level=2 andis_debug=false; then running ninja -C out/Release base_unittests && out/Release/base_unittests.)

Let’s first attempt type confusion between a list and a dictionary Value :

Here we’re creating a parent list base::Value, appending a child list, and then treating that child list as a dictionary instead — by trying to set a dictionary key. Oops. Here’s the crash:

[1021033:1021033:FATAL:values.cc(341)] Check failed: is_dict(). 
#0 0x5580847879b2 base::debug::CollectStackTrace()
#1 0x558084678a23 base::debug::StackTrace::StackTrace()
#2 0x55808469163f logging::LogMessage::~LogMessage()
#3 0x55808469240e logging::LogMessage::~LogMessage()
#4 0x55808477f129 base::Value::SetKey()
#5 0x5580842d51c7 base::ValuesTest_TypeConfusion_Test::TestBody()

Great! Nothing exploitable there. A nice crash.

Let’s try a data race. (Some would argue that this can’t be a race since it’s single-threaded, so call it ‘concurrent modification’ if you prefer).

Here we’re creating a list, adding three items, and iterating through it. Part way through the iteration — bang! — we modify the list by reserving more space.

We get:

[1055932:1055932:FATAL:checked_iterators.h(206)] Check failed: start_ == other.start_ (0x1c70005cda40 vs. 0x1c7000623000)
#0 0x5635d868bd72 base::debug::CollectStackTrace()
#1 0x5635d857cde3 base::debug::StackTrace::StackTrace()
#2 0x5635d85959ff logging::LogMessage::~LogMessage()
#3 0x5635d85967ce logging::LogMessage::~LogMessage()
#4 0x5635d75636a4 base::CheckedContiguousIterator<>::CheckComparable()
#5 0x5635d81d93fe base::ValuesTest_DataRace_Test::TestBody()

Nice. Again, a controlled crash. Let’s try a good old buffer overflow.

[1000598:1000598:FATAL:values.cc(899)] Check failed: index < storage_.size() (3 vs. 2)
#0 0x564f5d372382 base::debug::CollectStackTrace()
#1 0x564f5d260dc3 base::debug::StackTrace::StackTrace()
#2 0x564f5d27c00f logging::LogMessage::~LogMessage()
#3 0x564f5d27cdde logging::LogMessage::~LogMessage()
#4 0x564f5d367e0f base::Value::List::operator[]()
#5 0x564f5cf59315 base::ValuesTest_Overflow_Test::TestBody()

Great. Finally let’s try a use-after-free.

(This would be a use-after-free because appending something to the parent may cause the parent list to be reallocated, invalidating the child.)

[1056287:1056287:FATAL:values.cc(351)] Check failed: is_list(). 
#0 0x55ba66b37d72 base::debug::CollectStackTrace()
#1 0x55ba66a28de3 base::debug::StackTrace::StackTrace()
#2 0x55ba66a419ff logging::LogMessage::~LogMessage()
#3 0x55ba66a427ce logging::LogMessage::~LogMessage()
#4 0x55ba66b2ecff base::Value::Append()
#5 0x55ba666854dd base::ValuesTest_UseAfterFree_Test::TestBody()

Again a nice controlled crash… err, wait. Check failed: is_list() ? That’s a bit odd. Let’s flip is_asan=true in our gn arguments and try again:

==1069051==ERROR: AddressSanitizer: heap-use-after-free on address 0x6030001108a8 at pc 0x556351140274 bp 0x7fff66c8f1d0 sp 0x7fff66c8f1c8
READ of size 8 at 0x6030001108a8 thread T0
#0 0x556351140273 in absl::variant<absl::monostate, bool, int, base::Value::DoubleStorage, std::Cr::basic_string<char, std::Cr::char_traits<char>, std::Cr::allocator<char>>, std::Cr::vector<unsigned char, std::Cr::allocator<unsigned char>>, base::Value::Dict, base::Value::List>::index() const third_party/abseil-cpp/absl/types/variant.h:698:63
#1 0x556351140273 in base::Value::type() const base/values.h:311:54
#2 0x556351140273 in base::Value::is_list() const base/values.h:321:33
#3 0x556351140273 in base::Value::GetList() base/values.cc:351:3
#4 0x556351140273 in base::Value::Append(char const*) base/values.cc:1065:3
#5 0x556350000f21 in base::ValuesTest_UseAfterFree_Test::TestBody() base/values_unittest.cc:2626:9And finally,

Oh. We didn’t get a controlled crash this time; we got a potentially exploitable use-after-free.

Nevertheless this is a pretty good score. Even on a release build, three of the four of these coding errors result in a deliberate, controlled crash, rather than exploitable undefined behavior. This is marvelous, and base::Value is considered a very safe API.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK