Comment by segfaultbuserr

4 days ago

Corrupting program memory via malicious input data is known as a code-execution attack, not a data-only attack. The fuzzed program usually crashes because its executable code or the control flow got overwritten directly by the input, or indirectly by the program code itself when it tries to process bad data. An exploit involves injecting external code, or overwriting memory addresses (like a virtual table or a stack return address) to override the original logic flow to do something else.

A data-only attack would be an attack that reuses the original logic by only corrupting data inputs (such as a flag or a file path), without overwriting code or overriding the logic. W^X, stack canary, or CFI won't work in these cases since no code is tampered by the attacker. In almost ever talk about compiler mitigations, you always hear a passing-by mention of data-only attacks - before the speaker immediately dismisses them as an academic curiosity when the software industry is still facing a flood of stack smashing and ROP attacks.

> The fuzzed program usually crashes because its executable code or the control flow got overwritten directly by the input, or indirectly by the program code itself when it tries to process bad data.

Add assertions to your code. Voila, your run-of-the-mill fuzzer can now hunt for arbitrary problems with your program by turning them into crashes.

When fuzzing C programs, I usually also add undefined-behaviour sanitizers and friends, in the mode where they crash when you run into the kinds of UB they can detect.

  • Again, the point of a data-only attack is that you replace, say, a string with another valid string, and take control of program logic that way. For example, imagine a program that uses `system(LS_CMD_STR)`, where that LS_CMD_STR is some kind of constant holding the value "ls -lah". If an attacker can corrupt program memory in such a way that it overwrites that value with "rm / -f", no amount of assertions will trigger on this, but the program will do something much worse than expected.

    • In microcontroller programming, redundant data, checksumming and token-passing are sometimes used to mitigate CPU malfunctions due to electromagnetic interference (microcontrollers are often used as "programmable logic", so there's no hard layering between hardware and software, layering violation is made on purpose). If anything looks wrong, you trigger an assertion failure and reset the chip via the watchdog timer. For example, when you pass LS_CMD_STR, you would also pass the name of the caller and the CRC32 checksum of the string as arguments, and the function on the receiving side should validate them.

      So I think adding assertion is definitely a way to discover data-only attacks in fuzzing, or even as a partial mitigation of these attacks. It's just stack canary for variables and strings (but as the paper authors said, complete mitigation can be impractical).

TFA says memory corruption is a data-only attack, because it just overwrites data in memory.

  • It depends on what you overwrite. If you overwrite a CPU instruction or a function pointer that followed the buffer, it's a code-execution attack. If you overwrite a data variable that followed the buffer, it's a data-only attack. I said nearly all conventional fuzzing found code-execution attacks, not data-only attacks. Isn't that clear? The former method is considered common, well-studied, with defenses, the latter method is considered rare, niche, and defenseless.

    • Conventional fuzzing tries to overwrite executable code? I thought crashes are usually due to dereference of a corrupted pointer leading to unmapped memory.