Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Handles `.zon` invariant files:
### `cgen/ir.zig`

Core data structures:
- `Domain`: `.top`, `.values`, `.pointers`
- `Domain`: `.top`, `.values`, `.whole_values`, `.pointers`
- `Field`: Flattened field with name, width, dimensions, domain
- `Global`: Named global with dimensions and fields

Expand Down Expand Up @@ -156,8 +156,8 @@ Padding is detected from layout gaps and emitted as `._padN` fields.

The emitter produces C code with:
- Nested loops for array dimensions (global and field)
- Static arrays for `.values` and `.pointers` domains
- Index-based selection for constrained domains
- Static tables for `.values`, `.whole_values`, and `.pointers` domains
- Index-based selection for constrained domains; `.whole_values` copies one blob per field instance

## Code Style

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Absolution lets you specify an invariant for a program’s global state and fuzz

1. Parse globals from your C translation unit(s) using [aro](https://github.com/Vexu/aro).
2. Build flattened globals containing fields, padding, and domains.
3. Optionally apply a `.zon` invariant to constrain field values.
3. Optionally apply a `.zon` invariant to constrain field values (per-element `.values` / `.pointers`, or whole-field blobs with `.whole_values` on array-shaped fields; see [USAGE.md](USAGE.md)).
4. Emit `fuzzer.c` with sampling, invariant checking, and libFuzzer entrypoint.
5. Emit a symbol redefinition file for `objcopy` (handles `static` globals across translation units).
6. Write an optional seed file sized to the required random bytes.
Expand Down
37 changes: 34 additions & 3 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,17 @@ fields:

| Domain | Description | Fuzzer bytes used |
|--------|-------------|-------------------|
| `.top` | Unconstrained bytes from fuzzer input | `bit_width / 8` |
| `.values` | Fixed literal values (hex strings) | 1 (index selection) |
| `.pointers` | Addresses of listed symbols | 1 (index selection) |
| `.top` | Unconstrained bytes from fuzzer input | `element_bytes ×` global instances `×` field instances (see dimensions below) |
| `.values` | Fixed literal values (hex strings), **per scalar element** | `0` if there is at most one candidate, else `1` selector byte **per element** (each index in the field’s `.dims`, times global array instances) |
| `.whole_values` | Fixed set of **full field-instance** byte blobs (covers the entire field span, including all of the field’s `.dims`) | `0` if there is at most one candidate, else `1` selector byte **per field instance** (global array instances only; not once per inner array element) |
| `.pointers` | Addresses of listed symbols (per element, same indexing as `.values`) | Same selector rule as `.values` |

Constrained domains (`.values`, `.whole_values`, `.pointers`) allow at most **256** candidates; each multi-candidate domain uses a single selector byte to pick an index.

**When to use `.values` vs `.whole_values` for array-shaped fields**

- Use **`.values`** when each array element should be chosen independently from the same small set (or when the field is scalar). The sampler loops over dimensions and spends up to one selector byte per element.
- Use **`.whole_values`** when the entire array (or blob) must be one of a few fixed byte patterns end-to-end. Each candidate blob’s length must equal the field’s total byte span: `(bit_width / 8) × ∏` field dimension lengths. Do not rely on candidate string length alone to imply whole-field semantics; encode intent explicitly with `.whole_values`.

### Example

Expand Down Expand Up @@ -224,6 +232,29 @@ fields:
} }
```

Whole-field value example (`uint8_t b[8]` must be exactly one of two 8-byte patterns; one selector byte for the field, not eight):

```zig
.{.{
.name = "pkt",
.source_file = "my_module.c",
.size_bytes = 8,
.is_static = false,
.dims = .{},
.fields = .{.{
.name = ".b",
.offset_bits = 0,
.bit_width = 8,
.dims = .{.{ .len = 8, .stride_bytes = 1 }},
.is_padding = false,
.domain = .{ .whole_values = .{
"\x00\x01\x02\x03\x04\x05\x06\x07",
"\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
} },
}},
}}
```

### Field naming conventions

- **Scalar fields**: `.field_name`
Expand Down
5 changes: 5 additions & 0 deletions example/protocol_parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ decode → validate → process pipeline. The generated `LLVMFuzzerTestOneInput`
calls `sample_invariant()` to fill both structs from fuzzer input, then passes
the remaining bytes to `FuzzDecode()`.

To lock globals to specific value sets, export a `.zon` with `--zon`, edit
`.domain` (e.g. per-element `.values` or whole-array `.whole_values`), and pass
that file as the invariant when regenerating; see [USAGE.md](../../USAGE.md) for
domain encoding and fuzzer-byte accounting.

### Compile definitions

`decoder.c` uses a compile-time `PROTO_MAX_VERSIONS` define (guarded by
Expand Down
59 changes: 59 additions & 0 deletions src/Invariant.zig
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,65 @@ fn uniqueKey(alloc: std.mem.Allocator, name: []const u8, source_file: []const u8
return name;
}

test "applyToGlobals accepts whole_values domain" {
const allocator = std.testing.allocator;

var globals = std.ArrayList(Parser.Global).empty;
defer Parser.freeGlobals(allocator, &globals);

const parser_dims = try allocator.alloc(ir.Dimension, 1);
parser_dims[0] = .{ .len = 1, .stride_bytes = 2 };

const global_fields = try allocator.alloc(Parser.Field, 1);
global_fields[0] = .{
.name = try allocator.dupe(u8, "."),
.bit_width = 16,
.is_padding = false,
.dims = parser_dims,
.domain = .top,
};

try globals.append(allocator, .{
.name = try allocator.dupe(u8, "g"),
.source_file = try allocator.dupe(u8, ""),
.size_bytes = 2,
.is_static = false,
.dims = &.{},
.fields = global_fields,
});

var inv_arena = std.heap.ArenaAllocator.init(allocator);
const inv_alloc = inv_arena.allocator();
const inv_dims = try inv_alloc.alloc(ir.Dimension, 1);
inv_dims[0] = .{ .len = 1, .stride_bytes = 2 };
const inv_fields = try inv_alloc.alloc(ir.Field, 1);
inv_fields[0] = .{
.name = try inv_alloc.dupe(u8, "."),
.bit_width = 16,
.dims = inv_dims,
.domain = .{ .whole_values = &.{ &[_]u8{ 0xAA, 0xBB }, &[_]u8{ 0xCC, 0xDD } } },
.is_padding = false,
};
const inv_globals = try inv_alloc.alloc(ir.Global, 1);
inv_globals[0] = .{
.name = try inv_alloc.dupe(u8, "g"),
.source_file = try inv_alloc.dupe(u8, ""),
.size_bytes = 2,
.is_static = false,
.dims = &.{},
.fields = inv_fields,
};
var inv = Invariant{ .globals = inv_globals, .arena = inv_arena };
defer inv.deinit();

var apply_arena = std.heap.ArenaAllocator.init(allocator);
defer apply_arena.deinit();
const result = try inv.applyToGlobals(allocator, apply_arena.allocator(), globals);
defer allocator.free(result.func_symbols);
try std.testing.expect(globals.items[0].fields[0].domain == .whole_values);
try std.testing.expectEqual(@as(usize, 2), globals.items[0].fields[0].domain.whole_values.len);
}

test "applyToGlobals updates domains" {
const allocator = std.testing.allocator;

Expand Down
Loading
Loading