Browse OpenSSL punycode specs (10 of 19)
Proofs / OpenSSL punycode / The interface
api_decode_capacity
Nothing is written past the reported length, the reported length is exactly the capacity needed, and one element fewer is refused (CVE-2022-3602 was a write one element past the end).
- spec
- 42 lines of C
- with its headers
- 94 lines
- status
- Proved
- 10 of 19
On this page
The spec
The spec is the C program harness/api_decode_capacity.c, 42 lines, shown whole as it is in the repository at 7556530. It includes harness/laws_common.h (52 lines). Its first comment states the property in full, with every bound.
// Law api_decode_capacity: The output never goes past the capacity, and the length reported is exact. For any input
// (any octets, any length the allocator provides) and any capacity cap (any unsigned int), with an array of exactly
// cap elements holding any values: when ossl_punycode_decode returns 1 with *pout_length = count, the elements from
// count on keep their values; decoding the same input again into an array of exactly count elements returns 1 with
// the same count and code points, and into count - 1 elements (count > 0) returns 0. (CVE-2022-3602 was a write of
// one element past the end of this array when the code points did not fit.)
#include "laws_common.h"
int main(void) {
// Choose the inputs: any octets, any capacity, any initial contents of the array (and a copy of them)
size_t len = upto(PTRDIFF_MAX);
unsigned char *input = any_bytes(len);
unsigned int cap = nondet_u32();
unsigned int *out = any_uints(cap);
unsigned int *saved = malloc((size_t) cap * sizeof(unsigned int));
if (input == NULL || out == NULL || saved == NULL)
return 0;
for (unsigned int i = 0; i < cap; i++)
saved[i] = out[i];
unsigned int count = cap;
if (ossl_punycode_decode((const char *) input, len, out, &count) != 1)
return 0;
// Check: nothing written from count on
gp_assert(count <= cap && uints_eq(out + count, saved + count, cap - count));
// Check: count elements are enough...
unsigned int *exact = any_uints(count);
unsigned int exact_count = count;
if (exact == NULL)
return 0;
gp_assert(ossl_punycode_decode((const char *) input, len, exact, &exact_count) == 1);
gp_assert(exact_count == count && uints_eq(exact, out, count));
// ... and needed: one element fewer is too few
if (count > 0) {
unsigned int *fewer = any_uints(count - 1);
unsigned int fewer_count = count - 1;
if (fewer == NULL)
return 0;
gp_assert(ossl_punycode_decode((const char *) input, len, fewer, &fewer_count) == 0);
gp_assert(fewer_count == count - 1);
}
return 0;
}harness/laws_common.h, 52 lines
// laws_common.h: trivial helpers shared by the short laws (each law includes it once). No models, no algorithms.
#include <stdint.h>
#include <stdlib.h>
#include "quickproof.h"
#include "crypto/punycode.h" // ossl_punycode_decode, ossl_a2ulabel
// OpenSSL's allocator in this build (patch/shim/ossl_shim.c): set to 1 when CRYPTO_zalloc fails (malloc may fail at
// any call in the theorems), and the number of its blocks not freed yet.
extern int ossl_shim_zalloc_failed;
extern long ossl_shim_live;
static size_t upto(size_t max) { // any value from 0 to max
size_t value = nondet_size();
return value < max ? value : max;
}
static unsigned char *any_bytes(size_t n) { // a new object of n arbitrary bytes (NULL if malloc fails)
unsigned char *bytes = malloc(n);
if (bytes != NULL)
nondet_bytes(bytes, n);
return bytes;
}
static unsigned int *any_uints(size_t n) { // a new array of n arbitrary unsigned ints (NULL if malloc fails)
unsigned int *values = malloc(n * sizeof(unsigned int));
for (size_t i = 0; values != NULL && i < n; i++)
values[i] = nondet_u32();
return values;
}
static unsigned char *copy_of(const unsigned char *source, size_t n) { // a new object holding source[0..n-1]
if (source == NULL) // (NULL if source is or malloc fails)
return NULL;
unsigned char *copy = malloc(n);
for (size_t i = 0; copy != NULL && i < n; i++)
copy[i] = source[i];
return copy;
}
static int bytes_eq(const unsigned char *a, const unsigned char *b, size_t n) { // a[0..n-1] == b[0..n-1]
for (size_t i = 0; i < n; i++)
if (a[i] != b[i])
return 0;
return 1;
}
static int uints_eq(const unsigned int *a, const unsigned int *b, size_t n) { // a[0..n-1] == b[0..n-1]
for (size_t i = 0; i < n; i++)
if (a[i] != b[i])
return 0;
return 1;
}
static size_t last_delimiter(const unsigned char *s, size_t n) { // the position of the last '-', 0 if none
size_t last = 0;
for (size_t i = 0; i < n; i++)
if (s[i] == '-')
last = i;
return last;
}The theorem
QuickProof generated this statement from the spec, in src/contracts/api_decode_capacity.v:
Theorem api_decode_capacity : harness_safe whole_prog.src/contracts/laws.v states it on the spec's own program, whole_prog (the spec linked with the code under test), with its proof:
Theorem api_decode_capacity : harness_safe OpensslPunycode.contracts.api_decode_capacity.whole_prog.
Proof. exact (proj1 (proj2 (proj2 (proj2 (proj2 OpensslPunycode.contracts.suite.suite))))). Qed.Read as a statement about runs, it says that no run of the program has undefined behaviour or a failing check, for every value the spec can draw. That reading rests on VST's adequacy theorem, which is not yet instantiated in Rocq (formerly Coq) for QuickProof's environment.
The C files (units) its program links
crypto/punycode.cpatch/units/packet_ext.cpatch/shim/ossl_shim.cquickproof/libc/libc.c
What it leaves out
- What the first comment of the spec excludes, and its bounds.
- Like every spec of this proof: termination (a run that never returns is not a counterexample), one thread, and binaries built by a compiler (the theorem is about the code as CompCert defines C for x86-64, in a release build, compiled against a small header shim instead of OpenSSL's generated headers).
- The rest of OpenSSL: only
crypto/punycode.cand the parts ofcrypto/packet.cit reaches are code under test.
The summary page says what the proof as a whole covers.
How it was proved
AI agents wrote the proof, and Rocq checks it. qp verify passes on this repository: this statement is the one generated from the spec at the contract commit, and its proof uses only Rocq's standard axioms and VST's abstract malloc library.