ANT-2026-C6E98TYX · liboqs

other high

Severity Claude high · Security research firm high · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.

ANT-2026-C6E98TYX: 01-liboqs-ntru-uninit-fn-ptr

The NTRU constructors in src/kem/ntru/kem_ntru_.c call OQS_MEM_malloc(sizeof(OQS_KEM)) and assign twelve struct fields but omit encaps_derand at offset 88, leaving it with stale heap residue. The public API OQS_KEM_encaps_derand() at kem.c:645 checks only that kem != NULL before tail-jumping through kem->encaps_derand (compiled as jmp *0x58(%rax)). An attacker who can influence the 112-byte tcache bin before OQS_KEM_new("NTRU-") controls the jump target and all four pointer arguments passed to it. Demonstrated as a reliable (20/20) same-process control-flow hijack to system() on a Release build with glibc malloc; an ML-KEM-512 differential under identical heap groom confirms the NTRU constructor's missing initialization is the sole causal variable.

Target

Project: liboqs
Version: commit ab0e07c3 (upstream open-quantum-safe/liboqs)
Location: src/kem/kem.c:645 (OQS_KEM_encaps_derand); src/kem/ntru/kem_ntru_*.c:11 (six constructors)

Technical Details

ASAN: "SEGV on unknown address ... caused by a READ memory access ... dereference of a high value address" at kem.c:645 — the 0xbe poison pattern in encaps_derand proves the field was never written post-allocation. Root cause is a code-generation template gap: 33 of 39 KEM constructors set encaps_derand, but the six NTRU constructors predate the _derand API extension and were never updated. The dispatcher's guard checks only the struct pointer, not the function pointer it dereferences, so stale heap bytes become the target of a compiler-blessed indirect jump.

Crash trace:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==74==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x564e3eba1e6b bp 0x7fffe2b6a6f0 sp 0x7fffe2b6a6e8 T0)
==74==The signal is caused by a READ memory access.
==74==Hint: this fault was caused by a dereference of a high value address (see register values below).  Dissassemble the provided pc to learn which register was used.
    #0 0x564e3eba1e6b in OQS_KEM_encaps_derand /workspace/repo/src/kem/kem.c:645
    #1 0x564e3eba081f in main /tmp/poc.c:45
    #2 0x7f6dd8e27d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #3 0x7f6dd8e27e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #4 0x564e3eba0544 in _start (/tmp/poc-asan+0x12544)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /workspace/repo/src/kem/kem.c:645 in OQS_KEM_encaps_derand
==74==ABORTING

Reproduction

  1. Allocate 112-byte chunks and write the desired jump target (e.g., &system) at offset 88 of each
  2. Free the chunks so they populate glibc's 112-byte tcache freelist (LIFO)
  3. Call OQS_KEM_new(OQS_KEM_alg_ntru_hps2048509) — constructor reclaims a groomed chunk via OQS_MEM_malloc and writes fields 0–87 and 96–111, leaving offset 88 untouched
  4. kem->encaps_derand now holds the attacker-planted pointer
  5. Call OQS_KEM_encaps_derand(kem, payload, ...) — dispatcher's NULL check on kem passes, tail-call jmp *0x58(%rax) transfers to attacker target with payload in %rdi

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-C6E98TYX.


Reference: ANT-2026-C6E98TYX
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Ada Logics.

Verdict
true positive
Severity
high
TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-31 Reported to tracker
  2. 2026-05-07 Sent to maintainer
  3. 2026-05-12 Maintainer acknowledged
  4. 2026-06-11 Patch released
  5. 2026-08-17 Publicly revealed
PROVENANCE

SHA-3-512 hash:

036d8ef5dc823244713d07925545a3daa8e7f2b0dcb640d5e603e0b5af1ee56b0006b0a2cc12123774a58528d66c64171413eaaa9c6d62df3ede3fc27b30d600

Committed 2026-05-07 00:08 PT

Revealed 2026-08-17 10:47 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-C6E98TYX",
  "bug_class": "Uninitialized Function Pointer (CWE-824)",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-04-01T02:20:52+00:00",
  "description": "The NTRU constructors in src/kem/ntru/kem_ntru_*.c call OQS_MEM_malloc(sizeof(OQS_KEM)) and assign twelve struct fields but omit encaps_derand at offset 88, leaving it with stale heap residue. The public API OQS_KEM_encaps_derand() at kem.c:645 checks only that kem != NULL before tail-jumping through kem->encaps_derand (compiled as `jmp *0x58(%rax)`). An attacker who can influence the 112-byte tcache bin before OQS_KEM_new(\"NTRU-*\") controls the jump target and all four pointer arguments passed to it. Demonstrated as a reliable (20/20) same-process control-flow hijack to system() on a Release build with glibc malloc; an ML-KEM-512 differential under identical heap groom confirms the NTRU constructor's missing initialization is the sole causal variable.",
  "discovered_at": null,
  "location": "src/kem/kem.c:645 (OQS_KEM_encaps_derand); src/kem/ntru/kem_ntru_*.c:11 (six constructors)",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "liboqs",
  "reproduction": [
    "1. Allocate 112-byte chunks and write the desired jump target (e.g., &system) at offset 88 of each",
    "2. Free the chunks so they populate glibc's 112-byte tcache freelist (LIFO)",
    "3. Call OQS_KEM_new(OQS_KEM_alg_ntru_hps2048509) — constructor reclaims a groomed chunk via OQS_MEM_malloc and writes fields 0–87 and 96–111, leaving offset 88 untouched",
    "4. kem->encaps_derand now holds the attacker-planted pointer",
    "5. Call OQS_KEM_encaps_derand(kem, payload, ...) — dispatcher's NULL check on kem passes, tail-call `jmp *0x58(%rax)` transfers to attacker target with payload in %rdi"
  ],
  "technical_details": "ASAN: \"SEGV on unknown address ... caused by a READ memory access ... dereference of a high value address\" at kem.c:645 — the 0xbe poison pattern in encaps_derand proves the field was never written post-allocation. Root cause is a code-generation template gap: 33 of 39 KEM constructors set encaps_derand, but the six NTRU constructors predate the _derand API extension and were never updated. The dispatcher's guard checks only the struct pointer, not the function pointer it dereferences, so stale heap bytes become the target of a compiler-blessed indirect jump.",
  "title": "01-liboqs-ntru-uninit-fn-ptr",
  "vendor_severity": "high"
}