Skip to content

Security: sothis/bovnar

SECURITY.md

Security Policy

The Bovnar parser is intended to be pointed at untrusted bytes arriving over a socket. Anything reachable from bvnr_read() on hostile input is handled as a security report rather than an ordinary bug.

Reporting a Vulnerability

Do not open a public issue. Email bovnar@mail.de with the details and a reproducer, and allow time for a fix before disclosing.

Please include, where you have them:

  1. The output of bovnar version — the library version and the supported spec version.
  2. Platform, architecture and compiler, including whether the build was sanitized (-DBVNR_SANITIZE=ON).
  3. A reproducer. Attach the input as a file rather than pasting it: a document containing an octet stream carries NUL and unpaired CR and does not survive copy-paste, and a corrupted one reproduces a different problem.
  4. The observed effect — the sanitizer report, the crashing stack, or the incorrect value, with the reported line and column where the parser named one.

Expect an acknowledgement within a few days. A fix ships in the next release, with the reporter credited in CHANGELOG.md unless they ask otherwise.

Supported Versions

Version Supported
2.2.x Yes
2.1.x No — upgrade to 2.2.x; no C symbol changed, so the ABI is unchanged and C consumers are a drop-in replacement; the Python API only gains DocumentFeed.set_unit_policy
2.0.x No — upgrade to 2.2.x; the C ABI is unchanged (the exports were only added), so C consumers are a drop-in replacement; the Rust crate has one source-incompatible ReadOptions change
1.1.x No — upgrade to 2.2.x, which reads 1.0 and 1.1 documents (see below for what it now refuses); the C ABI and the Python API changed at 2.0.0, so this one is a rebuild and not a drop-in
1.0.x No

The format and the reference implementation are versioned separately, both semantically, and a fix lands on the newest release of the implementation only — on main, and on its maintenance branch where it also affects that release line (2.2.x); older lines are not patched. See CONTRIBUTING's Releases section. Within a major version of the format it is additive: a document valid under spec 1.0 stays valid, and decodes to the same values, under every 1.x spec, so a newer reader does not need a document migrated — with the exceptions the specification's §17 lists for 1.2: two corrections (whitespace inside a type-annotation parameter, and a bare array mixing units of one dimension, are now refused) and a list of reader defects that let malformed input through or, in two array rules, judged a document by the order or nesting of its elements. The implementation's own major moves when a program built against it has to change, which is what 2.0.0 was; CHANGELOG.md says which spec each release implements.

In Scope

  • Memory-safety defects — out-of-bounds access, use-after-free, uninitialized reads — reachable from the reader, the writer, the DOM, or the unit and datetime utilities, and unsoundness reachable through the safe API of the Rust crate or through the Python bindings.
  • A crash, hang, or unbounded allocation on attacker-controlled input, including input that is malformed, truncated, or deliberately adversarial.
  • A value that decodes to something other than what the document says, or a document that is accepted where the specification requires it to be rejected. A unit or a type that is silently wrong is a correctness defect with security consequences, not a cosmetic one.
  • Anything the specification's Security Considerations describes as prevented and that turns out not to be — see doc/03_bovnar_spec.md and the corresponding section of doc/ietf/draft-sonntag-bovnar-00.md.

Out of Scope

These are documented properties of the format, not defects. They are listed so a report does not have to guess.

  • Resource exhaustion under a default configuration. The reader's max_file_size defaults to no total-byte cap, and max_text_bytes and max_array_items default to 2³¹ − 1. A consumer of untrusted input is expected to set every limit of spec §12.2 to what it can afford — max_file_size, max_text_bytes, max_array_items and the two nesting depths above all (64 by default in C; the Python and Rust bindings default to 255, the format's limit). See the limits section of the specification.
  • Data loss in recovery mode. -c / resync deliberately skips a broken assignment and continues. A consumer that does not check the recovery counters cannot tell a complete document from a partial one; that is the documented trade-off of the mode.
  • Corruption of an octet stream in transit. Chunks are length-prefixed, so a channel that rewrites bytes — line-ending normalisation above all — desynchronises them unrecoverably. Use a binary-safe transport, or refuse the binary half outright with --text-only.
  • What a payload means. An octet stream is opaque bytes. A consumer that interprets or executes one is responsible for validating it.
  • Confusable or non-normalized keys. Keys are compared as bytes; the parser applies no Unicode normalisation and does not reject look-alike identifiers.
  • The document itself. A Bovnar document is passive data: no scripting, no macros, no external entity references. A &.path reference names a value in the same document and triggers no network or filesystem access, so it is not an SSRF or XXE vector.

Hardening

BVNR_HARDEN is on by default (stack protector on the CLI, _FORTIFY_SOURCE=2, -Wformat security checks). CI runs the test suite a second time under ASan and UBSan with -fno-sanitize-recover=all (all but the Python tests, which load the library into an unsanitized interpreter), and the reader, push-fed reader, writer, DOM and utility layers each have a self-contained fuzz harness registered as a CTest target, with optional libFuzzer and AFL++ builds behind BVNR_FUZZ_EXTERNAL.

GCC has no MemorySanitizer, so uninitialized heap reads are not covered by the sanitizer runs above; a clang/MSan finding is therefore especially welcome.

There aren't any published security advisories