Skip to content

Provide permission introspection as a library - #434

Draft
jcp19 wants to merge 5 commits into
masterfrom
claude/gobra-perm-introspect-sa2hdp
Draft

Provide permission introspection as a library#434
jcp19 wants to merge 5 commits into
masterfrom
claude/gobra-perm-introspect-sa2hdp

Conversation

@jcp19

@jcp19 jcp19 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Viper provides permission introspection, but Gobra doesn't yet. This PR introduces a library that encodes permission introspection and drops the use of inhales that were used before to provide a concrete permission value to locations for which we only had wildcard permission, so that we could perform operations such as copy and append.

claude added 4 commits August 18, 2026 08:53
Several Gobra operations (`copy`, `append`, `range` loops) must be given a
permission expression and cannot be applied with a wildcard amount, even
though that would be perfectly sound. So far, the codebase worked around
this by inhaling a fixed permission amount whenever only a wildcard was
held, e.g. `inhale acc(x, R19)` after `assert acc(x, _)`. That assumes
strictly more than `acc(x, _)` justifies: it fixes a concrete lower bound
on the amount held, and the constant has to be chosen so that it does not
clash with the other permissions to `x` that are around.

This commit replaces those inhales with permission introspection: given
`acc(x, _)`, `PermIntrospectT(x)` names the amount currently held and
returns it as a symbolic `p` constrained only by `0 < p`. Callers use `p`
wherever a permission expression is required. Introspecting does not give
up the wildcard, so no clean-up is needed afterwards to recover it.

The functions are axioms, but semantically justified ones: `acc(x, _)`
denotes some positive amount, so a positive `p` with `acc(x, p)` always
exists; Viper simply cannot bind that amount to a program variable.

`permsintrospect` imports nothing, so that any package may import it
without introducing an import cycle, which Gobra rejects. Introspection
functions whose signature mentions a type or predicate of another package
Q therefore live in Q, in a file named `perm-introspect.gobra`:

- `verification/utils/slices/perm-introspect.gobra` for the `Bytes`
  predicate, since `slices` must stay free to introspect permissions
  itself;
- `router/perm-introspect.gobra` for `map[uint16]*net.UDPAddr`, since
  that map type is specific to the dataplane and `permsintrospect` would
  otherwise have to import the `net` stubs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSKDgMaZVp35UqtekdhKYQ
Add `PermIntrospectBytes` to `permsintrospect`, which introspects the
permission held to the elements of a `[]byte` over a range. It is stated
with a quantified permission, since that is how access to slice elements
is expressed, and returns a single amount for the whole range, which is
what makes it usable to introspect a predicate abstracting over such a
range.

With that primitive in place, `slices.PermIntrospectBytes` no longer has
to be an axiom of its own: it unfolds the `Bytes` instance held with a
wildcard, introspects the resulting permission to the elements, and folds
the predicate back with the amount that was named. One fewer assumption,
and the proof obligation now also acts as a check on the primitive.

This makes `slices` a client of `permsintrospect`, so importing `slices`
from `permsintrospect` would now be a genuine import cycle rather than a
hypothetical one; the package documentation is updated accordingly.

`router.PermIntrospectAddrsMap` stays an axiom: the permission to a map is
atomic, so there is nothing to unfold and introspect at a more primitive
level, and Gobra has no generics that would let `permsintrospect` declare
one map primitive for arbitrary key and value types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSKDgMaZVp35UqtekdhKYQ
Replace the range-shaped `[]byte` axiom with the simpler per-element one:
given a wildcard to `&s[idx]`, name the amount held. It is easier to read
and to justify than a quantified assumption.

Deriving the range version from it is not a one-liner, though. The axiom
names an amount for one element, and the amounts it returns for two
different elements are unrelated, so a single call cannot establish a
uniform amount over a range -- and uniformity is exactly what folding a
predicate that abstracts over that range requires. So
`PermIntrospectBytesRange` now recurses over the range, introspecting each
element and keeping the smallest of the results: holding `acc(&s[i], p_i)`
for every `i` implies holding `acc(&s[i], m)` for `m` the minimum of the
`p_i`. The recursion is what pays for the simpler axiom.

`slices.PermIntrospectBytes` is unchanged in shape: it unfolds the `Bytes`
instance, calls the range lemma, and folds back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSKDgMaZVp35UqtekdhKYQ
It has no call site, and the permission to a slice header is not something
the codebase has needed to name so far. It also sat confusingly next to
`PermIntrospectBytes`, which introspects the elements of a byte slice
rather than the header. Every axiom here is an assumption, so an unused one
is not free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSKDgMaZVp35UqtekdhKYQ
@jcp19 jcp19 changed the title Add permission introspection utilities for wildcard access Provide permission introspection as a library Aug 18, 2026
Comment on lines +308 to +312
// Unfortunately, it is not possible to call `copy` with a wildcard amount, even though that would be
// perfectly fine. Instead of assuming a fixed permission amount, we introspect the one we already
// hold and pass the resulting symbolic, positive amount to `copy`.
// @ ghost zeroPerm := sl.PermIntrospectBytes(zeroInitVector[:], 0, len(zeroInitVector[:]))
// @ unfold acc(sl.Bytes(zeroInitVector[:], 0, len(zeroInitVector[:])), zeroPerm)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Unfortunately, it is not possible to call `copy` with a wildcard amount, even though that would be
// perfectly fine. Instead of assuming a fixed permission amount, we introspect the one we already
// hold and pass the resulting symbolic, positive amount to `copy`.
// @ ghost zeroPerm := sl.PermIntrospectBytes(zeroInitVector[:], 0, len(zeroInitVector[:]))
// @ unfold acc(sl.Bytes(zeroInitVector[:], 0, len(zeroInitVector[:])), zeroPerm)
// It is not possible to call `copy` with a wildcard amount. As suuch, we use permission
// introspection to soundly obtain a symbolic positive permission amount for `zeroInitVector`.
// @ ghost zeroPerm := sl.PermIntrospectBytes(zeroInitVector[:], 0, len(zeroInitVector[:]))
// @ unfold acc(sl.Bytes(zeroInitVector[:], 0, len(zeroInitVector[:])), zeroPerm)

//
// The functions below have no body; they are axioms. They are semantically
// justified: `acc(x, _)` denotes *some* positive permission amount, so a positive
// `p` with `acc(x, p)` always exists. Viper simply has no way of binding that

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename file to introspect.gobra

Permission introspection cannot serve a range loop over a map. Gobra
desugars such a loop into code that exhales a hard-coded
`acc(m, 1/MapExhalePermDenom)` before entering it (Desugar.scala), so the
loop demands an amount bounded from below by a concrete constant. Naming
the amount held does not establish that bound -- introspection only
guarantees the amount is positive -- so no symbolic amount can discharge
it, and neither can the wildcard the site actually holds.

`processIntraBFD` therefore goes back to its original encoding, byte for
byte, with only a comment added recording why the inhale has to stay and
why the obvious improvement does not work. `PermIntrospectAddrsMap` is
removed along with it, since nothing uses it any more and an unused axiom
is not free.

The package documentation claimed range loops among the cases
introspection serves. That was wrong, so it now carries an explicit
limitation section instead. `copy` and `append` are unaffected: they take
the amount as an ordinary expression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSKDgMaZVp35UqtekdhKYQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants