Skip to content

Mid-object pointer loses its offset across a non-inlined call, causing false valid-deref alarms (31 tasks, -496) #571

Description

@leventeBajczi

A mid-object pointer passed across a real (non-inlined) call boundary loses its offset, and the
callee's dereference checks then report a spurious valid-deref violation. This is the largest
wrong-verdict family in the latest full run: 31 of 68 wrong verdicts, worth −496 score.

Reproducer

// a mid-object heap pointer crossing a real (non-inlined, because recursive) call boundary
#include <stdlib.h>
void peek(char *p, int n) {
  if (n == 0) return;
  char c = *p;        // always within the 5-byte buffer
  (void)c;
  peek(p + 1, n - 1);
}
int main() {
  char *s = malloc(5);
  peek(s, 5);         // touches s[0..4] only
  free(s);
  return 0;
}

peek(s, 5) touches s[0..4] of a 5-byte buffer and never leaves it, yet:

--backend CEGAR -> (Property valid-deref) (SafetyResult Unsafe)

Isolation

The failure needs both pointer arithmetic and a call boundary — either alone is handled correctly:

variant pointer arithmetic call boundary verdict truth
reproducer above yes yes Unsafe SAFE
peek(p, n-1) — pointer never advances no yes Safe SAFE
same walk written as a loop in main yes no Safe SAFE

The call boundary has to be real: a non-recursive helper gets inlined and the problem disappears.
Recursion is simply the cheapest way to keep a call un-inlined, since InlineProceduresPass refuses
a procedure that transitively reaches recursion. The recursion bound is not involved — the false
alarm is identical at --force-unroll-recursion −1, 3, 10 and 20.

Confirmed on the real task termination-recursive-malloc/rec_strcopy_malloc (expected true,
reported false(valid-deref)): rewriting its recursive rec_strcopy_helper as an equivalent loop,
with everything else unchanged, flips the verdict from Unsafe to Safe.

Affected families in the last full run

31 tasks: termination-15 (14), ldv-memsafety (4), list-ext-properties (2), memsafety (2),
uthash-2.0.2 (2), termination-memory-alloca, termination-recursive-malloc, memsafety-ext3.

Relation to the split model

ReferenceElimination.seedSplitParams seeds a callee's split pointer parameter as
p_base := p; p_offset := 0, which presumes the caller passed a plain offset-0 pointer. For
address-taken locals that case is refused loudly (see
PointerArithmeticTest."passing a split pointer as a function argument is still refused"). The
reproducer above uses heap pointers, which do not go through the split path — so here the same
wrong premise produces a silent false alarm instead of an exception.

Scope

Not a regression against 7.3.0: these tasks were TIMEOUT or ERROR there, so the family predates the
8.x line. Filed out of #564, where it accounts for most of the remaining wrong verdicts.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions