cybersecurity · intermediate · ~15 min · safe pentest lab

Extract the BLE Complete Local Name from an advertisement

Bounds-safe TLV walking with overflow checks at every step.

Challenge

Extract the local name from a BLE advertisement by walking its TLV records — bounds-safe TLV walking is a pattern that recurs in BLE, DHCP, ICMPv6 options, and X.509.

Task

Implement int extract_local_name(const uint8_t *adv, size_t n, char *out, size_t cap) that finds the Local Name record and copies its value into out.

Input

  • adv, n: a fixture advertisement byte buffer and its length, baked into the harness. It is a sequence of TLV records, each [len][type][value... (len-1 bytes)]. Because len covers type + value, the next record starts at offset + len + 1.
  • out, cap: the output buffer and its capacity.

Output

Returns int: the number of value bytes copied (>= 0) on success, or -1 on failure. On a Complete Local Name (type 0x09) or Shortened Local Name (type 0x08), copy its value bytes into out (bounded by cap - 1, NUL-terminated).

Example

adv with a 0x09 record whose value is "MyDev"   ->   out = "MyDev", returns 5
no name record present                            ->   -1

Edge cases

  • Any NULL pointer or cap == 0 returns -1.
  • A record whose length would walk past n returns -1.
  • A len == 0 record is malformed (and would loop forever) — return -1.
  • A name that does not fit in cap (need room for the NUL) returns -1.

Rules

  • Bounds-check every byte read.
  • The name's value length is len - 1 (the type byte takes one).

Why this matters

TLV walking is a foundational pattern that shows up in BLE, DHCP, ICMPv6 options, X.509 extensions — get it right once and the rest fall out.

Input format

A const byte buffer, its length, a bounded output buffer, and its capacity.

Output format

Number of name bytes copied (>= 0) on success, -1 otherwise.

Constraints

Every byte read must be bounds-checked. NUL-terminate the output. Fail on len==0.

Starter code

#include <stdint.h>
#include <stddef.h>
int extract_local_name(const uint8_t *adv, size_t n, char *out, size_t cap) {
    /* TODO */
    (void)adv; (void)n; (void)out; (void)cap;
    return -1;
}

Common mistakes

Forgetting that len covers type+value (so the value is len - 1 bytes). Allowing len == 0. Reading the value before bounds-checking.

Edge cases to handle

Empty buffer (n == 0). Single record with no name. Name field at the very end of the buffer.

Complexity

O(n) — visits each byte at most once.

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.