Secure Coding in C · intermediate · ~15 min

Shape of a libFuzzer-compatible defensive target

- Write a function that matches libFuzzer's exact `LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)` signature. - Parse a length-prefixed (TLV) record stream while treating every byte of input as hostile. - Add bounds checks so the parser never reads past `size`, no matter what bytes arrive. - Handle the classic malformed-input cases — empty input, `NULL` data, a length that overruns the buffer, and a zero length — without crashing or looping forever. - Understand why a good fuzz target is deterministic, allocation-clean, and free of global side effects. - Explain how one small function becomes a portable contract that libFuzzer, AFL++, and Honggfuzz can all drive unchanged.

Overview

Fuzzing is the practice of feeding a program a flood of random, malformed, and unexpected inputs to see what makes it crash. Each crash is a bug the fuzzer found for you — often a memory-safety bug an attacker could exploit. To use a fuzzer, you write one small function called a fuzz target: it receives a blob of bytes and runs your parsing or decoding logic on them. The fuzzer supplies millions of different blobs per second and watches for anything that goes wrong.

This lesson teaches the shape of that function and the defensive discipline it demands. You already know how to follow a pointer safely from the pointers lesson, and how to guard array and buffer accesses from the bounds-checks lesson. A fuzz target is where those two skills become non-negotiable: the caller (the fuzzer) is deliberately trying to hand you input that breaks your assumptions. If a single access is missing a bounds check, the fuzzer will find it.

We parse a tiny TLV (type-length-value) record format so the logic stays small and the safety reasoning stays visible. We are not launching a live fuzzer inside this lesson's harness; instead we build the function's shape and prove by hand that it survives every hostile input. Once the shape is correct, wiring it to a real fuzzer is a one-line compile step. In plain terms: you write a function that can never be tricked into reading memory it doesn't own — and that is exactly what a fuzz target must be.

Why it matters

Fuzzing is one of the highest-return security tools in modern software. Google's OSS-Fuzz has found tens of thousands of bugs in critical open-source projects — OpenSSL, SQLite, libpng, curl, the Linux kernel's userspace libraries — many of them memory-corruption flaws that could have become remote exploits. The entry point into all of that machinery is the humble LLVMFuzzerTestOneInput function.

The reason the shape matters so much: the signature is a portable contract. Write your logic once behind that signature and libFuzzer, AFL++, and Honggfuzz can each drive it without you changing a line. That is a huge multiplier — you get coverage-guided fuzzing, corpus minimization, crash reproduction, and continuous integration fuzzing essentially for free.

But the contract cuts both ways. A fuzz target that crashes on legitimate malformed input (rather than on a real bug) wastes everyone's time chasing false positives. A target that leaks memory or writes to globals produces flaky, unreproducible results. Learning to write a clean, bounds-safe, side-effect-free target is what separates fuzzing that finds real bugs from fuzzing that finds noise. And the defensive habits you build here — never trust a length field, never advance without checking — are exactly the habits that keep parsers safe in production, not just under a fuzzer.

Core concepts

1. The fuzz-target signature is a contract

Definition. A fuzz target is a single function with this exact signature:

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

Plain-language explanation. The fuzzer hands you a pointer to size bytes of input. Your job is to run whatever parsing/decoding logic you want to test on those bytes, then return. The name and types are fixed — the fuzzer's runtime looks up this exact symbol by name at link time and calls it in a tight loop.

How it works internally. When you compile with -fsanitize=fuzzer, the compiler links in a main() provided by the libFuzzer runtime. That main() generates inputs (guided by code coverage), and for each one calls your LLVMFuzzerTestOneInput. Coverage instrumentation records which branches your code took, so the fuzzer learns which inputs reach new code and mutates toward them.

   libFuzzer runtime main()
           |
           |  generate/mutate an input blob
           v
   +-----------------------------+
   |  data ---> [ b0 b1 b2 ... ] |   size = N
   +-----------------------------+
           |  call
           v
   LLVMFuzzerTestOneInput(data, size)   <-- YOUR code
           |  return 0
           v
   record coverage, mutate, repeat  (millions/sec)

When to use / not use. Use this shape for anything that parses untrusted bytes: file formats, network packets, decompressors, deserializers. Do not put slow I/O, network calls, or randomness inside it — the function is called millions of times and must be fast and deterministic.

Pitfall. Returning anything other than 0 is undefined-by-convention; libFuzzer reserves non-zero returns for future use. Always return 0.

Knowledge check. In your own words, why must LLVMFuzzerTestOneInput be deterministic — that is, produce the same behavior every time it sees the same data/size?

2. Treat data and size as fully hostile

Definition. Every byte in data, and the value of size itself, is attacker-controlled. There are no guarantees: size can be 0, data can be NULL (when size is 0), and the bytes can be any pattern at all.

Plain-language explanation. In normal code you often assume the caller passes sensible values. Here that assumption is the bug. The whole point of the fuzzer is to violate your assumptions faster than you can imagine them.

How it works internally. The buffer data points to is exactly size bytes and not one byte more. Reading data[size] is an out-of-bounds read — undefined behavior — and under a sanitizer it aborts immediately. The fuzzer is specifically trying to make you read past that boundary.

When to use / not use. Always validate before reading. The rule from the bounds-checks lesson applies to every single access: prove the index is in [0, size) before you dereference.

Pitfall. The most common overrun is reading a multi-byte field near the end of the buffer: you check that pos < size but then read two bytes starting at pos, and the second byte is at pos + 1 == size. Always check that all the bytes you are about to read fit.

Knowledge check (find-the-bug). if (pos < size) { type = data[pos]; len = data[pos + 1]; } — with size == 5 and pos == 4, which read is out of bounds and why?

3. The TLV record format

Definition. TLV = type-length-value. Our toy record is laid out as:

  byte 0      byte 1        bytes 2 .. (len)
 +--------+  +--------+  +----------------------+
 |  len   |  |  type  |  |   payload (len-1)    |
 +--------+  +--------+  +----------------------+
  ^ total length of record CONTENTS (type + payload)
  • len (1 byte): the total number of content bytes in this record — the type byte plus the payload.
  • type (1 byte): a tag. We count records whose type == 0x01.
  • payload (len - 1 bytes): the record's data, which we skip over here.

So a whole record occupies 1 + len bytes in the stream: one byte for the len field itself, then len content bytes.

Plain-language explanation. Length-prefixed formats are everywhere — Bluetooth advertisements, TLS records, DNS packets. The parser reads a length, then trusts it to know how far to jump. That trust is precisely where overruns hide: a malicious len says "jump 200 bytes forward" when only 3 bytes remain.

How it works internally. Parsing is a loop with a cursor pos. Each iteration: read len at pos, verify the whole record fits (pos + 1 + len <= size), look at type, then advance pos += 1 + len. The advance uses the attacker's len, so it must be validated first.

When to use / not use. This exact skeleton is reusable for any length-prefixed format. Do not hardcode record sizes — the length field is the whole point of TLV.

Pitfall. A len of 0 means the record has zero content bytes, so pos advances by only 1. If you ever advance by len instead of 1 + len, a zero length advances by 0 and the loop spins forever.

Malformed input What a naive parser does What a defensive parser does
size == 0 reads data[0] (overrun) returns 0 immediately
len points past buffer end reads payload out of bounds stops, returns count so far
len == 0 may loop forever stops (or advances safely by 1)
trailing partial record reads missing bytes stops when it doesn't fit

Knowledge check (predict-the-output). Given bytes 02 01 AA 03 01 BB CC (hex), how many type == 0x01 records are there, and where does each record start?

Syntax notes

The signature must match exactly — same name, same parameter types, return int. The types come from <stdint.h> (uint8_t) and <stddef.h> (size_t).

#include <stdint.h>   /* uint8_t  */
#include <stddef.h>   /* size_t   */

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    /* data: pointer to `size` untrusted bytes (may be NULL when size==0) */
    /* size: number of bytes; can be 0                                    */

    size_t pos = 0;                 /* cursor into the stream          */
    while (pos < size) {            /* only enter if a byte is present */
        uint8_t len = data[pos];    /* safe: pos < size proven above   */
        if (len == 0) break;        /* zero length would not advance   */
        if (pos + 1 + len > size)   /* does the whole record fit?      */
            break;                  /* no -> stop, don't read past end */
        /* ... inspect data[pos + 1] (the type) ... */
        pos += 1 + len;             /* advance past len byte + content */
    }
    return 0;                       /* libFuzzer expects 0             */
}

Two details matter. First, pos + 1 + len is computed in size_t (unsigned), and because len is at most 255 there is no overflow risk against a realistic size — but the order of the check matters: verify the fit before touching data[pos + 1] or the payload. Second, size_t is unsigned, so never write size - 1 without first checking size > 0, or the subtraction wraps to a huge number.

Lesson

Why this matters

A fuzz target is a single function. The popular fuzzers — libFuzzer, AFL++, and Honggfuzz — all drive their target through the same signature:

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

This signature is a contract. Your function must:

  • Accept any data and size without crashing.
  • Return 0 (libFuzzer ignores any other return value).
  • Not write to global variables.
  • Not allocate memory without freeing it.

We are not running a real fuzzer in this harness. Instead, we write the shape of a defensive target and prove it survives difficult inputs.

The record format we will parse

We use a small toy format. Each record looks like this:

[ len : u8 ][ type : u8 ][ payload : len - 1 bytes ]
  • len is one byte: the total length of the record's contents.
  • type is one byte: a tag identifying the record.
  • payload is len - 1 bytes (the bytes after type).

The fuzz target counts the records whose type == 0x01.

Edge cases that must NOT crash

A defensive target has to handle malformed input gracefully:

  • size == 0 → return 0.
  • data == NULL (with size 0) → return 0.
  • A record's len walks past size → stop counting and return what we have so far. Never read past the end of the buffer.
  • len == 0 → stop. A zero length would never advance, causing an infinite loop.

Your job

Implement:

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

It should walk the record stream, never read past size, and return the count of type == 0x01 records.

What this is NOT

  • It is not a live fuzzer driver. The harness calls the function with hand-crafted inputs to prove it survives them all.
  • It is not an exploit generator. A fuzz target finds crashes; it does not weaponise them.

Code examples

#include <stdint.h> #include <stddef.h> #include <stdio.h> #include <string.h>

/*

  • Defensive fuzz target: walks a TLV record stream and counts records
  • whose type byte == 0x01. Survives ANY (data, size) without crashing.
  • Record layout in the stream:
  • [ len : u8 ][ type : u8 ][ payload : len-1 bytes ]
  • A whole record occupies (1 + len) bytes: the len byte plus len
  • content bytes (type + payload). */ int LLVMFuzzerTestOneInput(const uint8_t data, size_t size) { if (data == NULL || size == 0) / nothing to parse -> not a bug */ return 0;
int count = 0;
size_t pos = 0;

while (pos < size) {             /* guaranteed: data[pos] is readable */
    uint8_t len = data[pos];     /* content length of this record     */

    if (len == 0)                /* zero-length record cannot advance */
        break;                   /* stop rather than loop forever     */

    /* Whole record = 1 (len byte) + len (content). Must fit in buffer. */
    if (pos + 1 + (size_t)len > size)
        break;                   /* truncated record -> stop, no OOB   */

    uint8_t type = data[pos + 1];/* safe: fit was proven above         */
    if (type == 0x01)
        count++;

    pos += 1 + (size_t)len;      /* advance past this record           */
}

/*
 * A real fuzz target returns 0 and the fuzzer only cares about crashes.
 * We keep `count` so a test harness (or a demo main) can observe it.
 */
(void)count;
return 0;

}

/*

  • The real libFuzzer runtime supplies main() and calls the function above
  • millions of times. So we can see the behavior here, this demo main()
  • feeds a few hand-crafted inputs and prints the count each produced.
  • Compile without -fsanitize=fuzzer to run this demo. */ static int count_type01(const uint8_t data, size_t size) { / Same logic, but returns the count so we can print it in the demo. */ if (data == NULL || size == 0) return 0; int count = 0; size_t pos = 0; while (pos < size) { uint8_t len = data[pos]; if (len == 0) break; if (pos + 1 + (size_t)len > size) break; if (data[pos + 1] == 0x01) count++; pos += 1 + (size_t)len; } return count; }

int main(void) { /* well-formed: two records, both type 0x01 / const uint8_t good[] = {0x02, 0x01, 0xAA, 0x03, 0x01, 0xBB, 0xCC}; / len (0x09) overruns the 3-byte buffer -> must stop, not read OOB / const uint8_t overrun[] = {0x09, 0x01, 0xAA}; / zero length -> must break, not loop forever */ const uint8_t zero_len[] = {0x00, 0x01, 0x01};

printf("good     -> %d type-0x01 records\n", count_type01(good, sizeof good));
printf("overrun  -> %d type-0x01 records\n", count_type01(overrun, sizeof overrun));
printf("zero_len -> %d type-0x01 records\n", count_type01(zero_len, sizeof zero_len));
printf("empty    -> %d type-0x01 records\n", count_type01(NULL, 0));

/* Also drive the real target signature to show it returns 0 cleanly. */
printf("target(good) returned %d\n", LLVMFuzzerTestOneInput(good, sizeof good));
return 0;

}

/*

  • Expected output:
  • good -> 2 type-0x01 records
  • overrun -> 0 type-0x01 records
  • zero_len -> 0 type-0x01 records
  • empty -> 0 type-0x01 records
  • target(good) returned 0
  • Edge cases covered: NULL data, size 0, a length that overruns the
  • buffer, and a zero length. None of them read past the buffer or hang. */

Line by line

We trace the good input {0x02, 0x01, 0xAA, 0x03, 0x01, 0xBB, 0xCC} — 7 bytes — through count_type01 (identical logic to the target, but it returns the count so we can watch it).

  1. data != NULL and size == 7, so we skip the early return.
  2. count = 0, pos = 0.
  3. Iteration 1pos (0) < 7, enter loop. len = data[0] = 0x02 (this record has 2 content bytes). len != 0. Fit check: pos + 1 + len = 0 + 1 + 2 = 3, and 3 <= 7, so the record fits. type = data[1] = 0x01 → it's a match, count = 1. Advance: pos += 1 + 2 = 3.
  4. Iteration 2pos (3) < 7, enter. len = data[3] = 0x03 (3 content bytes). Fit check: 3 + 1 + 3 = 7 <= 7, fits exactly. type = data[4] = 0x01 → match, count = 2. Advance: pos += 1 + 3 = 4, so pos = 7.
  5. Iteration 3pos (7) < 7 is false. Loop ends. Return 2.
pos (start) len = data[pos] fit? (pos+1+len ≤ size) type = data[pos+1] count new pos
0 0x02 3 ≤ 7 ✓ 0x01 1 3
3 0x03 7 ≤ 7 ✓ 0x01 2 7
7 loop exits (7 < 7 false) 2

Now trace the overrun input {0x09, 0x01, 0xAA} — 3 bytes:

  1. data != NULL, size == 3.
  2. Iteration 1pos (0) < 3. len = data[0] = 0x09 (claims 9 content bytes). len != 0. Fit check: 0 + 1 + 9 = 10, and 10 <= 3 is false → the record does not fit → break. We never read data[1..9], so no out-of-bounds access. Return 0.

The attacker put a large len hoping we'd trust it and read 9 bytes from a 3-byte buffer. The fit check catches exactly that. For zero_len {0x00, ...}, the very first len = 0x00 triggers if (len == 0) break;, so we stop before any infinite loop. For the empty case, size == 0 returns immediately.

Common mistakes

Mistake 1 — checking the length field after reading past it.

/* WRONG: reads type before confirming the record fits */
uint8_t len  = data[pos];
uint8_t type = data[pos + 1];      /* pos+1 may already be == size */
if (pos + 1 + len <= size) { ... } /* too late */

Why it's wrong: with size == 1 and pos == 0, data[pos + 1] is data[1], one byte past the end — an out-of-bounds read the fuzzer will find instantly. Fix: prove the whole record fits before touching any byte beyond data[pos]:

uint8_t len = data[pos];
if (len == 0 || pos + 1 + (size_t)len > size) break;
uint8_t type = data[pos + 1];      /* now guaranteed safe */

Recognize it by asking, for every data[...], "what proved this index is < size?"

Mistake 2 — advancing by len instead of 1 + len.

pos += len;   /* WRONG: forgets the len byte itself */

Why it's wrong: two failures at once. It miscounts record boundaries, and when len == 0 it advances by 0 — an infinite loop that hangs the fuzzer (which reports it as a timeout crash). Fix: pos += 1 + (size_t)len; and always guard len == 0 separately.

Mistake 3 — size - 1 without checking size > 0.

for (size_t i = 0; i < size - 1; i++) { ... }  /* WRONG when size == 0 */

Why it's wrong: size is unsigned (size_t). When size == 0, size - 1 wraps to SIZE_MAX (a gigantic number), so the loop runs almost forever and reads wildly out of bounds. Fix: guard if (size == 0) return 0; first, or restructure as i + 1 < size.

Mistake 4 — assuming data is a valid pointer when size == 0.

libFuzzer may pass data == NULL with size == 0. Dereferencing it crashes. Fix: the if (data == NULL || size == 0) return 0; guard at the top.

Debugging tips

Compiler errors.

  • unknown type name 'uint8_t' / 'size_t' — you forgot #include <stdint.h> and/or #include <stddef.h>.
  • A warning like comparison of integer expressions of different signedness usually means you're comparing a signed int cursor with an unsigned size — make the cursor size_t.

Runtime errors (what a sanitizer reports).

  • heap-buffer-overflow READ of size 1 from AddressSanitizer means you read a byte past data + size. Look at the access it names and ask which check should have proven that index in range. Build with -fsanitize=address,undefined -g so the report includes line numbers.
  • A libFuzzer timeout or out-of-memory report often means an infinite loop (forgot the len == 0 guard) or an allocation inside the target that isn't freed.

Logic errors.

  • Count comes out too high/low: print pos, len, and type each iteration and hand-check against your input bytes, exactly like the trace table above.
  • The parser stops early on valid input: your fit check may be off by one — remember a record occupies 1 + len bytes, not len.

Reproducing a fuzzer crash. libFuzzer writes the crashing input to a file like crash-<hash>. Re-run your target on just that file (./target crash-<hash>) under a debugger to step through it.

Questions to ask when it doesn't work. Did I check the fit before every multi-byte read? Is my cursor unsigned? Do I advance by 1 + len? Is there any input length that makes an arithmetic expression wrap? Does size == 0 reach a data[...] access?

Memory safety

This lesson is a memory-safety lesson: a fuzz target's entire reason to exist is to expose undefined behavior. The concerns for this topic:

  • Out-of-bounds reads. Every data[i] must be provably in [0, size). The defensive pattern is: check the index (or the whole span you're about to read) before the access, never after. Reading even one byte past size is undefined behavior and, under AddressSanitizer, an immediate abort.
  • Unsigned wraparound. size and pos are size_t (unsigned). Subtractions like size - 1 or size - needed wrap to enormous values when the left side is smaller. Always establish size >= needed before subtracting, or rewrite as addition (pos + needed <= size).
  • Integer overflow in the fit check. pos + 1 + len could in theory overflow size_t, but since len <= 255 and pos <= size, it's safe for any realistic buffer. If a format allowed multi-byte lengths, you would need an explicit overflow-safe check such as len <= size - pos - 1 (with size - pos - 1 guarded to be non-negative).
  • Infinite loops / hangs. A cursor that can fail to advance (zero length, or advancing by a computed value that can be 0) is a denial-of-service. Guard it and always make forward progress.
  • NULL dereference. data may be NULL when size == 0; guard it.
  • No side effects, no leaks. Don't write to globals (breaks reproducibility) and don't allocate without freeing (libFuzzer reports it as an OOM/leak). Keep the target allocation-free per call where you can, or free everything before returning.

Defensive-security framing. These are the same bugs attackers weaponize in real parsers — a trusted length field is the classic vector behind Heartbleed-style over-reads. Writing the fit check correctly here is the defensive habit that prevents that class of vulnerability. This target only counts records; it never emits or acts on attacker data, so there is nothing to weaponize. That is the right posture: fuzz targets find crashes; they do not exploit them.

Real-world uses

Where this shows up. Google's OSS-Fuzz continuously fuzzes hundreds of open-source projects, and every one of them exposes exactly this LLVMFuzzerTestOneInput entry point. Image libraries (libpng, libjpeg-turbo), crypto (OpenSSL, BoringSSL), databases (SQLite), compression (zlib, brotli), and protocol parsers (curl, c-ares) all ship fuzz targets shaped precisely like the one in this lesson. When a target finds a crash, the project gets an automatic bug report with a reproducer input — often before the code ever ships.

Beginner best practices.

  • Match the signature exactly and return 0.
  • Add the NULL/size == 0 guard first.
  • Bounds-check every access; advance by the full record size.
  • Keep the target small, deterministic, and allocation-clean.
  • Give variables meaningful names (pos, len, count) so the safety reasoning is legible.

Advanced best practices.

  • Build a seed corpus of valid inputs so the fuzzer starts from meaningful structure instead of random noise.
  • Use LLVMFuzzerInitialize for one-time setup (never per-call work).
  • Reach for a FuzzedDataProvider to carve typed values out of the raw bytes cleanly.
  • Run under -fsanitize=address,undefined (and MemorySanitizer for uninitialized-read detection) in CI so any regression is caught immediately.
  • Add structure-aware or grammar-based fuzzing for formats where random bytes rarely pass early validation.
  • Keep each target focused on one parser so crashes point to a single component.

Practice tasks

Beginner 1 — Enough input? Write int enough_input(size_t size, size_t needed) that returns 1 if size >= needed, else 0. Requirements: no subtraction (avoid unsigned wraparound), no other logic. Example: enough_input(4, 8)0; enough_input(8, 8)1. Concepts: size_t comparison, the length-guard habit. Hint: it is a single comparison.

Beginner 2 — Bounds-safe byte read. Write int safe_byte(const unsigned char *data, size_t size, size_t idx) that returns data[idx] (0–255) if idx < size, otherwise returns -1. Requirements: never read out of bounds; handle data == NULL by returning -1. Example: for {0x41, 0x42}, safe_byte(d, 2, 1)66, safe_byte(d, 2, 5)-1. Concepts: bounds check before dereference.

Intermediate 1 — Count a specific type. Starting from the lesson's target, write a version that counts records whose type equals a value you choose (say 0x02) and returns that count from a helper. Requirements: reuse the fit check and len == 0 guard; test it against {0x02, 0x02, 0xAA, 0x02, 0x01, 0xBB}. Expected: one type == 0x02 record. Concepts: TLV walking, bounds checks.

Intermediate 2 — Total payload bytes. Write a target-shaped function that, instead of counting, sums the total payload length (len - 1 per record) across all well-formed records and stops safely at the first truncated one. Requirements: never read past size; len == 0 stops the walk; return 0 from the target but expose the sum via a helper for testing. Example: {0x02, 0x01, 0xAA, 0x03, 0x01, 0xBB, 0xCC} → payload total 1 + 2 = 3. Concepts: arithmetic on validated lengths, size_t safety.

Challenge — Two-byte length field. Extend the format so len is a two-byte, big-endian field (len = (data[pos] << 8) | data[pos+1]), followed by type and payload. Write a bounds-safe target that walks this stream. Requirements: read the two length bytes only after proving pos + 2 <= size; then prove the whole record (2 + len bytes) fits before advancing; guard against len == 0; and write the fit check so it cannot wrap even though len can now be up to 65535. Hint: prefer len > size - pos - 2 after checking size - pos >= 2, rather than pos + 2 + len > size, to reason clearly about overflow. Concepts: multi-byte fields, overflow-safe bounds checks, endianness.

Summary

A fuzz target is one small function with a fixed signature — int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) — and matching that shape turns your parser into something libFuzzer, AFL++, and Honggfuzz can all drive unchanged. The function must treat every byte of data and the value of size as hostile: guard NULL/size == 0, bounds-check every access before reading, advance a cursor by the full record size (1 + len), and never trust a length field without proving the whole record fits.

The most important syntax is the signature itself plus the fit check if (pos + 1 + (size_t)len > size) break; and the if (len == 0) break; guard. The most common mistakes are reading a multi-byte field before checking it fits, advancing by len instead of 1 + len (which loops forever on a zero length), and writing size - 1 when size might be 0 (unsigned wraparound). Remember: keep the target deterministic, allocation-clean, and free of global writes, and always return 0. Master these bounds habits here and you have the exact discipline that keeps real-world parsers safe from the length-field over-reads attackers love to exploit.

Practice with these exercises