Skip to main content

Memory Safety, Fuzzing, and Specialized Execution

Competency Contract​

  • Primary competency: AR-ARCHITECTURE-001
  • Mastery: explain, apply, develop, and evaluate as required by the semester assessment
  • Status: required evidence extension; 30–40 hours explicitly included in the 440–640-hour semester budget

Required Work​

Use sanitizers and fuzzing to reproduce memory failures; compare a bounded C component with a memory-safe Rust design; explain exploit mitigations; measure one SIMD, GPU, or WebAssembly-oriented execution constraint.

Evidence Contract​

  • sanitizer trace
  • minimized fuzz case
  • C/Rust safety comparison
  • mitigation note
  • specialized-platform benchmark.

Assessment Integration​

  • Project: produce the evidence in the semester repository rather than as detached prose.
  • Checkpoint: show the evidence exists and identify its highest-risk gap.
  • Cumulative review: connect the work to current, previous, and two-semesters-back knowledge.
  • Exam: evaluate a new case and reject an attractive but unsafe or unsupported alternative.
  • Rubric: every required evidence category must pass; strengths cannot average away a missing category.
  • Answer key: calibrate the shape of evidence without prescribing one implementation.

Oral Defense​

Explain what the safe-language version prevents, what it does not, and why the measured platform path helps or hurts.

AI-Use Declaration​

Record tools and models used, material output accepted, rejected suggestions, and independent verification. Undeclared generated evidence fails the integrity gate.

Reading Route​

Start with the linked semester concepts and labs. Use local books selectively for conceptual depth and current official standards for normative requirements. Stop reading when you can produce and defend the required evidence.

Bounded execution route​

This route adds experiments to existing work, not five unrelated projects. Follow the workload contract. All artifacts are required; unsupported toolchains are recorded as a setup gap, not silently graded as complete.

1. Fuzzing, oracle and minimization — 10–14 hours​

Reuse Module 1's line reader. Create a deterministic in-memory source callback over fuzz bytes; reserve the first byte to select capacity 1–16 and use the remaining bytes as the stream. Allocate exactly capacity bytes so AddressSanitizer can detect writes beyond the logical capacity. Invoke the reader repeatedly until EOF; bound attempts by payload length plus one. Reset state for every fuzz input and never depend on the previous input.

Build a separate oracle that splits at the first newline: embedded NUL outranks length, the terminator uses one capacity byte, and a final nonempty unterminated line can succeed. Check status, output bytes and consumed offset. A sanitizer catches some memory violations; it cannot establish these semantic properties. Preserve separate I/O-error tests from the module because byte-only fuzzing does not inject callback errors.

Use a Clang installation with libFuzzer support. Compile your adapter and the reader with -g -O1 -fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all; the adapter supplies LLVMFuzzerTestOneInput, not main. Start with empty, newline-only, NUL and exact-capacity seeds. Run a bounded campaign such as -runs=10000 -max_len=256; retain compiler version, command, corpus and log. An unavailable runtime requires a supported local toolchain or course VM, not invented output.

On a separate disposable copy, change the reader guard used < capacity - 1 to used < capacity. Use exact-capacity data to expose the terminator write beyond the allocated buffer. Never ship this mutant. Reduce a failing input by deleting bytes while preserving the same sanitizer failure; record each accepted reduction, the final capacity/payload and reproduction command. A locally irreducible input is not necessarily globally minimal. Restore the correct reader and rerun both the minimized regression and campaign.

Acceptance: trace identifies the invalid write, harness has a semantic oracle, fault reproduces from a retained file, minimized case fails the mutant and passes the repaired reader. Zero fuzz findings alone cannot pass. Reusing an old sanitizer trace is allowed, but the new fault/minimization evidence must be real.

2. Safe-language comparison — 8–10 hours​

Port a bounded slice parser matching the reader's no-I/O subset to safe Rust using byte slices and an explicit result enum. Do not convert arbitrary bytes to UTF-8 strings. Return consumed count and result; retain the same precedence and final-line behavior. Compare C and Rust on the retained corpus and capacity boundaries, and explain each intentional API difference.

Create a small separate borrow-checker exercise: hold a reference to a Vec element, grow the Vec, then use the old reference. Capture the compiler rejection, then repair by using the reference before growth or reacquiring it afterward. Explain why safe Rust prevents this aliasing pattern, why C needs an ownership rule, and why neither language automatically enforces your parser's status precedence. Discuss panic, allocation/resource exhaustion and unsafe/FFI boundaries; do not claim Rust prevents all bugs.

Acceptance: executable matching cases, compile rejection and repair, and a specific distinction between memory safety and semantic correctness. A prose-only language comparison is insufficient.

3. Mitigation reasoning — 4–6 hours​

Use the minimized bug to write a one-page threat and mitigation note. Distinguish root-cause repair from diagnostic instrumentation and exploit mitigations. For stack canaries, address randomization and nonexecutable data pages, state the attack mechanism each aims to obstruct and one limitation. These controls do not make an out-of-bounds access defined or repair a parser contract. Record actual build/platform evidence separately from assumed defaults; do not disable host protections or build an exploit.

Acceptance: classify the bug, explain the repair invariant, and reject the claim that passing ASan or enabling a mitigation proves safety. State unknowns honestly.

4. SIMD constraint experiment — 8–10 hours​

The default specialized-platform route needs no GPU. Extend the Module 3 locality lab using the same kernels and checked checksum. Compare Clang -O2 with -O2 -fno-vectorize -fno-slp-vectorize, keeping target, input, repetitions and all other flags fixed. Retain -Rpass=loop-vectorize -Rpass-missed=loop-vectorize -Rpass-analysis=loop-vectorize output and target assembly. Inspect whether packed/vector instructions actually appear; flags express a policy, not proof of the generated code.

Build both versions outside the timed region; alternate which binary runs first across at least eight paired trials. Preserve raw samples at dimensions 128, 511 and 512, compiler/CPU identity, order and clock resolution. Reuse the lab's independent checksum and increase repetition within its documented limits if timings are unresolved. Keep sanitizer builds separate from timing builds. Explain reduction width, contiguous versus strided access and tail handling for nonmultiple dimensions.

Acceptance: measured constraint on vectorization with generated-code evidence, correct sums and defensible uncertainty. A slower result or a documented failure to vectorize can pass if supported by diagnostics and timing; claiming SIMD acceleration without generated-code evidence cannot. GPU or WebAssembly alternatives must meet the same prediction, correctness, raw timing and platform-limit contract and stay within the allocation.

Runnable safety workshop​

Download the native safety lab and extract native-safety-validation. Read README-safety.md before executing anything. This workshop supports the existing fuzzing and safe-language routes; their 10–14 and 8–10 hours remain inside the existing strand allocation. It does not add another project or waive the mitigation and platform work above.

Prepare predictions and an implementation​

Start from your Module 1 reader and write the in-memory adapter yourself. Before reading reader.rs, sketch a Rust result enum and a slice-based parser. Predict each read's status, output and cumulative input offset for these cases:

CapacityPayload bytes, in hexDistinction to defend
142Space for payload versus space for the C terminator
241 00Embedded NUL and overlength at the same time
241 0a 42Newline consumption and a final unterminated line
2ff 0aArbitrary bytes versus UTF-8 text
041Rejection without consuming input

The C implementation owns no output memory: the caller provides it. The Rust reference returns a borrowed slice tied to the input lifetime, without writing a terminator. Its capacity parameter preserves the C acceptance budget for the comparison. It has no callback I/O errors or null pointers; those remain separate C tests. Explain these differences before calling the implementations equivalent.

Execute and inspect the reference evidence​

Use Python 3.9+, a working Clang/libFuzzer/ASan/UBSan toolchain and Rust edition 2021. The package README gives the tested Ubuntu/WSL setup and troubleshooting. From the extracted directory, with the tested compiler name:

python3 safety.py --clang clang-22 --output evidence/first-run

Choose a new evidence directory each time. The runner records versions, source hashes, commands, errors, corpus and comparison cases. It executes deterministic tests, actual coverage-guided campaigns, a temporary C fault and its repair, and the C/Rust comparison against an independent Python oracle. Finite passing inputs are evidence for those cases, not a proof of safety or full equivalence. The broken C source and executables are temporary; the original reader is intact.

Inspect the discovered input, the exact invalid write in the ASan trace, each deletion attempt and the repaired replay. A fuzzer may already discover a two-byte irreducible case; no successful deletion is required to establish that each remaining single-byte deletion stops reproducing the failure. Do not claim global minimality or relabel an unrelated crash as the intended fault.

Then inspect the safely compiled Rust precedence mutant. It deliberately gives length priority over NUL in some cases. The independent oracle must reject it: agreement between two programs would be insufficient if both shared that defect.

Explain the borrow rejection and make a fresh transfer​

borrow-rejected.rs holds a reference to a Vec element, calls push, then uses the reference. Locate E0502 and the three relevant expressions. The two repairs either finish using the reference before mutation or retain an index and borrow again afterward. push requires mutable access even if capacity happens to be available; rejection does not prove reallocation occurred in a particular run.

After studying the references, apply compare.c to your own C reader and adapt your Rust implementation to the documented capacity:hex input protocol. Keep the independent oracle unchanged. Add a fresh combined NUL/newline/length case and a boundary not in your initial predictions; retain the disagreement and repair if one occurs. Explain why safe Rust can still violate a result contract, panic on an invalid index, exhaust resources, or cross an unsafe/FFI boundary.

Completion evidence: your predictions, source changes and assistance record; real crash/replay and reduction evidence; executable C/Rust observations; borrow rejection and repair; and a fresh-case defense. Running the supplied reference packet alone does not pass the learner gate. Keep the existing mitigation and specialized-platform evidence separately visible.

Calibration and fresh-case assessment​

Score sanitizer trace, minimized fuzz case, C/Rust comparison, mitigation note and platform benchmark separately from 0–3 using the workload contract. Every category needs 2. Ask why a larger backing allocation can conceal a logical-capacity violation from ASan, why a corpus is not proof of all inputs, why Rust can still implement the wrong precedence, and why compiler vectorization permission does not guarantee a speedup. A strong answer names the mechanism and proposes a discriminating test.

Targeted primary resources​

Primary pages checked on 2026-10-23. Existing K&R, SICP and architecture-book selections remain the backbone. No new book purchase is required. Learners must produce their own experiment results; these instructions are not a precompleted evidence packet.