Writing Robust C - Best Practices for Finding and Preventing Vulnerabilities

10 months ago

For EIP-4844, Ethereum clients request the quality to compute and verify KZG commitments. Rather than each lawsuit rolling their ain crypto, researchers and developers came unneurotic to constitute c-kzg-4844, a comparatively tiny C room with bindings for higher-level languages. The thought was to make a robust and businesslike cryptographic room that each clients could use. The Protocol Security Research squad astatine the Ethereum Foundation had the accidental to reappraisal and amended this library. This blog station volition sermon immoderate things we bash to marque C projects much secure.

Fuzz

Fuzzing is simply a dynamic codification investigating method that involves providing random inputs to observe bugs successful a program. LibFuzzer and afl++ are 2 fashionable fuzzing frameworks for C projects. They are some in-process, coverage-guided, evolutionary fuzzing engines. For c-kzg-4844, we used LibFuzzer since we were already well-integrated with LLVM project's different offerings.

Here's the fuzzer for verify_kzg_proof, 1 of c-kzg-4844's functions:

#include "../base_fuzz.h" static const size_t COMMITMENT_OFFSET = 0; static const size_t Z_OFFSET = COMMITMENT_OFFSET + BYTES_PER_COMMITMENT; static const size_t Y_OFFSET = Z_OFFSET + BYTES_PER_FIELD_ELEMENT; static const size_t PROOF_OFFSET = Y_OFFSET + BYTES_PER_FIELD_ELEMENT; static const size_t INPUT_SIZE = PROOF_OFFSET + BYTES_PER_PROOF; int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { initialize(); if (size == INPUT_SIZE) { bool ok; verify_kzg_proof( &ok, (const Bytes48 *)(data + COMMITMENT_OFFSET), (const Bytes32 *)(data + Z_OFFSET), (const Bytes32 *)(data + Y_OFFSET), (const Bytes48 *)(data + PROOF_OFFSET), &s ); } instrumentality 0; }

When executed, this is what the output looks like. If determination were a problem, it would constitute the input to disk and halt executing. Ideally, you should beryllium capable to reproduce the problem.

There's besides differential fuzzing, which is simply a method which fuzzes 2 oregon much implementations of the aforesaid interface and compares the outputs. For a fixed input, if the output is different, and you expected them to beryllium the same, you cognize thing is wrong. This method is precise fashionable successful Ethereum due to the fact that we similar to person respective implementations of the aforesaid thing. This diversification provides an other level of safety, knowing that if 1 implementation were flawed the others whitethorn not person the aforesaid issue.

For KZG libraries, we developed kzg-fuzz which differentially fuzzes c-kzg-4844 (through its Golang bindings) and go-kzg-4844. So far, determination haven't been immoderate differences.

Coverage

Next, we utilized llvm-profdata and llvm-cov to make a sum study from moving the tests. This is simply a large mode to verify codification is executed ("covered") and tested. See the coverage people successful c-kzg-4844's Makefile for an illustration of however to make this report.

When this people is tally (i.e., make coverage) it produces a array that serves arsenic a high-level overview of however overmuch of each relation is executed. The exported functions are astatine the apical and the non-exported (static) functions are connected the bottom.

There is simply a batch of greenish successful the array above, but determination is immoderate yellowish and reddish too. To find what is and isn't being executed, notation to the HTML record (coverage.html) that was generated. This webpage shows the full root record and highlights non-executed codification successful red. In this project's case, astir of the non-executed codification deals with hard-to-test mistake cases specified arsenic representation allocation failures. For example, here's immoderate non-executed code:

At the opening of this function, it checks that the trusted setup is large capable to execute a pairing check. There isn't a trial lawsuit which provides an invalid trusted setup, truthful this doesn't get executed. Also, due to the fact that we lone trial with the close trusted setup, the effect of is_monomial_form is ever the aforesaid and doesn't instrumentality the mistake value.

Profile

We don't urge this for each projects, but since c-kzg-4844 is simply a show captious room we deliberation it's important to illustration its exported functions and measurement however agelong they instrumentality to execute. This tin assistance place inefficiencies which could perchance DoS nodes. For this, we utilized gperftools (Google Performance Tools) alternatively of llvm-xray due to the fact that we recovered it to beryllium much feature-rich and easier to use.

The pursuing is simply a elemental illustration which profiles my_function. Profiling works by checking which acquisition is being executed each truthful often. If a relation is accelerated enough, it whitethorn not beryllium noticed by the profiler. To trim the accidental of this, you whitethorn request to telephone your relation aggregate times. In this example, we telephone my_function 1000 times.

#include <gperftools/profiler.h> int task_a(int n) { if (n <= 1) instrumentality 1; instrumentality task_a(n - 1) * n; } int task_b(int n) { if (n <= 1) instrumentality 1; instrumentality task_b(n - 2) + n; } void my_function(void) { for (int one = 0; one < 500; i++) { if (i % 2 == 0) { task_a(i); } other { task_b(i); } } } int main(void) { ProfilerStart("example.prof"); for (int one = 0; one < 1000; i++) { my_function(); } ProfilerStop(); instrumentality 0; }

Use ProfilerStart("<filename>") and ProfilerStop() to people which parts of your programme to profile. When re-compiled and executed, it volition constitute a record to disk with profiling data. You tin past usage pprof to visualize this data.

Here is the graph generated from the bid above:

Here's a bigger illustration from 1 of c-kzg-4844's functions. The pursuing representation is the profiling graph for compute_blob_kzg_proof. As you tin see, 80% of this function's clip is spent performing Montgomery multiplications. This is expected.

Reverse

Next, presumption your binary successful a bundle reverse engineering (SRE) instrumentality specified arsenic Ghidra oregon IDA. These tools tin assistance you recognize however high-level constructs are translated into low-level instrumentality code. We deliberation it helps to reappraisal your codification this way; similar however speechmaking a insubstantial successful a antithetic font volition unit your encephalon to construe sentences differently. It's besides utile to spot what benignant of optimizations your compiler makes. It's rare, but sometimes the compiler volition optimize retired thing which it deemed unnecessary. Keep an oculus retired for this, thing similar this really happened successful c-kzg-4844, some of the tests were being optimized out.

When you presumption a decompiled function, it volition not person adaptable names, analyzable types, oregon comments. When compiled, this accusation isn't included successful the binary. It volition beryllium up to you to reverse technologist this. You'll often spot functions are inlined into a azygous function, aggregate variables declared successful codification are optimized into a azygous buffer, and the bid of checks are different. These are conscionable compiler optimizations and are mostly fine. It whitethorn assistance to physique your binary with DWARF debugging information; astir SREs tin analyse this conception to supply amended results.

For example, this is what blob_to_kzg_commitment initially looks similar successful Ghidra:

With a small work, you tin rename variables and adhd comments to marque it easier to read. Here's what it could look similar aft a fewer minutes:

Static Analysis

Clang comes built-in with the Clang Static Analyzer, which is an fantabulous static investigation instrumentality that tin place galore problems that the compiler volition miss. As the sanction "static" suggests, it examines codification without executing it. This is slower than the compiler, but a batch faster than "dynamic" investigation tools which execute code.

Here's a elemental illustration which forgets to escaped arr (and has different occupation but we volition speech much astir that later). The compiler volition not place this, adjacent with each warnings enabled due to the fact that technically this is wholly valid code.

#include <stdlib.h> int main(void) { int* arr = malloc(5 * sizeof(int)); arr[5] = 42; instrumentality 0; }

The unix.Malloc checker volition place that arr wasn't freed. The enactment successful the informing connection is simply a spot misleading, but it makes consciousness if you deliberation astir it; the analyzer reached the instrumentality connection and noticed that the representation hadn't been freed.

Not each of the findings are that elemental though. Here's a uncovering that Clang Static Analyzer recovered successful c-kzg-4844 erstwhile initially introduced to the project:

Given an unexpected input, it was imaginable to displacement this worth by 32 bits which is undefined behavior. The solution was to restrict the input with CHECK(log2_pow2(n) != 0) truthful that this was impossible. Good job, Clang Static Analyzer!

Sanitize

Santizers are dynamic investigation tools which instrumentality (add instructions) to programs which tin constituent retired issues during execution. These are peculiarly utile astatine uncovering communal mistakes associated with representation handling. Clang comes built-in with respective sanitizers; present are the 4 we find astir utile and casual to use.

Address

AddressSanitizer (ASan) is simply a accelerated representation mistake detector which tin place out-of-bounds accesses, use-after-free, use-after-return, use-after-scope, double-free, and representation leaks.

Here is the aforesaid illustration from earlier. It forgets to escaped arr and it volition acceptable the 6th constituent successful a 5 constituent array. This is simply a elemental illustration of a heap-buffer-overflow:

#include <stdlib.h> int main(void) { int* arr = malloc(5 * sizeof(int)); arr[5] = 42; instrumentality 0; }

When compiled with -fsanitize=address and executed, it volition output the pursuing mistake message. This points you successful a bully absorption (a 4-byte constitute successful main). This binary could beryllium viewed successful a disassembler to fig retired precisely which acquisition (at main+0x84) is causing the problem.

Similarly, here's an illustration wherever it finds a heap-use-after-free:

#include <stdlib.h> int main(void) { int *arr = malloc(5 * sizeof(int)); free(arr); instrumentality arr[2]; }

It tells you that there's a 4-byte work of freed representation astatine main+0x8c.

Memory

MemorySanitizer (MSan) is simply a detector of uninitialized reads. Here's a elemental illustration which reads (and returns) an uninitialized value:

int main(void) { int data[2]; instrumentality data[0]; }

When compiled with -fsanitize=memory and executed, it volition output the pursuing mistake message:

Undefined Behavior

UndefinedBehaviorSanitizer (UBSan) detects undefined behavior, which refers to the concern wherever a program's behaviour is unpredictable and not specified by the langauge standard. Some communal examples of this are accessing out-of-bounds memory, dereferencing an invalid pointer, speechmaking uninitialized variables, and overflow of a signed integer. For example, present we increment INT_MAX which is undefined behavior.

#include <limits.h> int main(void) { int a = INT_MAX; instrumentality a + 1; }

When compiled with -fsanitize=undefined and executed, it volition output the pursuing mistake connection which tells america precisely wherever the occupation is and what the conditions are:

Thread

ThreadSanitizer (TSan) detects information races, which tin hap successful multi-threaded programs erstwhile 2 oregon much threads entree a shared representation determination astatine the aforesaid time. This concern introduces unpredictability and tin pb to undefined behavior. Here's an illustration successful which 2 threads increment a planetary counter variable. There aren't immoderate locks oregon semaphores, truthful it's wholly imaginable that these 2 threads volition increment the adaptable astatine the aforesaid time.

#include <pthread.h> int antagonistic = 0; void *increment(void *arg) { (void)arg; for (int one = 0; one < 1000000; i++) counter++; instrumentality NULL; } int main(void) { pthread_t thread1, thread2; pthread_create(&thread1, NULL, increment, NULL); pthread_create(&thread2, NULL, increment, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); instrumentality 0; }

When compiled with -fsanitize=thread and executed, it volition output the pursuing mistake message:

This mistake connection tells america that there's a information race. In 2 threads, the increment relation is penning to the aforesaid 4 bytes astatine the aforesaid time. It adjacent tells america that the representation is counter.

Valgrind

Valgrind is simply a almighty instrumentation model for gathering dynamic investigation tools, but its champion known for identifying representation errors and leaks with its built-in Memcheck tool.

The pursuing representation shows the output from moving c-kzg-4844's tests with Valgrind. In the reddish container is simply a valid uncovering for a "conditional leap oregon determination [that] depends connected uninitialized value(s)."

This identified an borderline case successful expand_root_of_unity. If the incorrect basal of unity oregon width were provided, it was imaginable that the loop volition interruption earlier out[width] was initialized. In this situation, the last cheque would beryllium connected an uninitialized value.

static C_KZG_RET expand_root_of_unity( fr_t *out, const fr_t *root, uint64_t width ) { out[0] = FR_ONE; out[1] = *root; for (uint64_t one = 2; !fr_is_one(&out[i - 1]); i++) { CHECK(i <= width); blst_fr_mul(&out[i], &out[i - 1], root); } CHECK(fr_is_one(&out[width])); instrumentality C_KZG_OK; }

Security Review

After improvement stabilizes, it's been thoroughly tested, and your squad has manually reviewed the codebase themselves aggregate times, it's clip to get a information reappraisal by a reputable information group. This won't beryllium a stamp of approval, but it shows that your task is astatine slightest somewhat secure. Keep successful caput determination is nary specified happening arsenic cleanable security. There volition ever beryllium the hazard of vulnerabilities.

For c-kzg-4844 and go-kzg-4844, the Ethereum Foundation contracted Sigma Prime to behaviour a information review. They produced this report with 8 findings. It contains 1 captious vulnerability successful go-kzg-4844 that was a truly bully find. The BLS12-381 room that go-kzg-4844 uses, gnark-crypto, had a bug which allowed invalid G1 and G2 points to beryllium sucessfully decoded. Had this not been fixed, this could person resulted successful a statement bug (a disagreement betwixt implementations) successful Ethereum.

Bug Bounty

If a vulnerability successful your task could beryllium exploited for gains, similar it is for Ethereum, see mounting up a bug bounty program. This allows information researchers, oregon anyone really, to taxable vulnerability reports successful speech for money. Generally, this is specifically for findings which tin beryllium that an exploit is possible. If the bug bounty payouts are reasonable, bug finders volition notify you of the bug alternatively than exploiting it oregon selling it to different party. We urge starting your bug bounty programme aft the findings from the archetypal information reappraisal are resolved; ideally, the information reappraisal would outgo little than the bug bounty payouts.

Conclusion

The improvement of robust C projects, particularly successful the captious domain of blockchain and cryptocurrencies, requires a multi-faceted approach. Given the inherent vulnerabilities associated with the C language, a operation of champion practices and tools is indispensable for producing resilient software. We anticipation our experiences and findings from our enactment with c-kzg-4844 supply invaluable insights and champion practices for others embarking connected akin projects.

View source