makiri 0.5.1-aarch64-linux → 0.6.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/libfuzzer.yml +1 -1
- data/.github/workflows/verify.yml +37 -0
- data/CHANGELOG.md +12 -0
- data/README.md +16 -0
- data/Rakefile +6 -0
- data/lib/makiri/3.2/makiri.so +0 -0
- data/lib/makiri/3.3/makiri.so +0 -0
- data/lib/makiri/3.4/makiri.so +0 -0
- data/lib/makiri/4.0/makiri.so +0 -0
- data/lib/makiri/version.rb +1 -1
- data/script/check_c_safety.rb +11 -0
- data/script/check_c_safety_allowlist.yml +9 -12
- data/suppressions/ruby.supp +22 -0
- data/verify/Makefile +237 -0
- data/verify/cbmc_models.c +38 -0
- data/verify/harness_alloc.c +257 -0
- data/verify/harness_buf.c +167 -0
- data/verify/harness_hash.c +28 -0
- data/verify/harness_span.c +213 -0
- data/verify/harness_spanbuf.c +98 -0
- data/verify/harness_utf8.c +70 -0
- data/verify/harness_utf8_chain.c +64 -0
- data/verify/harness_utf8_words.c +54 -0
- data/verify/harness_xml_chars.c +59 -0
- data/verify/harness_xml_expand.c +72 -0
- data/verify/harness_xml_parse.c +42 -0
- data/verify/harness_xpath_lex.c +51 -0
- data/verify/harness_xpath_number.c +68 -0
- data/verify/harness_xpath_parse.c +57 -0
- data/verify/selftest_main.c +16 -0
- data/verify/stub.c +10 -0
- data/verify/verify.h +54 -0
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c0a020c0e52029940d64a91b53ae459d08b1d53e3106c3b312b6c86601bc29a
|
|
4
|
+
data.tar.gz: 2d5bf5107d4de3892b70684aaad11b67f0f7eec74496e01132f79703db87262e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e0eda64911e48477808cf4b033b8f24fdbcb978558e27db9ea570bcd4364eb45e81e0a39b0c0e3ebe357aff622fb479e0c230b6c218f4f23ab760d3cd163cc8
|
|
7
|
+
data.tar.gz: 96f2da4e924ade762e57887c128bd70583330917a18f09abafd2721c2fdc61f647e5d1eb64544b8af753182c2e83d320b9dd2e02dcd384e3187ac27b3b27a352
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Verify
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
cbmc:
|
|
11
|
+
name: CBMC proofs
|
|
12
|
+
runs-on: ubuntu-24.04
|
|
13
|
+
timeout-minutes: 20
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout (with vendored Lexbor submodule)
|
|
17
|
+
uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
submodules: recursive
|
|
20
|
+
|
|
21
|
+
- name: Set up Ruby
|
|
22
|
+
uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: "3.4"
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Install CBMC
|
|
28
|
+
env:
|
|
29
|
+
CBMC_VERSION: "6.9.0"
|
|
30
|
+
run: |
|
|
31
|
+
curl -fsSLO "https://github.com/diffblue/cbmc/releases/download/cbmc-${CBMC_VERSION}/ubuntu-24.04-cbmc-${CBMC_VERSION}-Linux.deb"
|
|
32
|
+
sudo apt-get update
|
|
33
|
+
sudo apt-get install -y "./ubuntu-24.04-cbmc-${CBMC_VERSION}-Linux.deb"
|
|
34
|
+
cbmc --version
|
|
35
|
+
|
|
36
|
+
- name: Run verification suite
|
|
37
|
+
run: bundle exec rake verify
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-07-05
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
* Text and comment node content (`create_text_node`, `create_comment`,
|
|
8
|
+
`content=`) and attribute values (`[]=`, `set_attribute_ns`) now accept an
|
|
9
|
+
embedded NUL (U+0000) and store it verbatim, matching the WHATWG DOM and
|
|
10
|
+
browsers, instead of raising. Names, tag names, namespaces, CSS selectors, and
|
|
11
|
+
XPath expressions stay NUL-strict, and invalid UTF-8 is still rejected. XML
|
|
12
|
+
documents (`Makiri::XML`) are unchanged and still reject NUL everywhere, since
|
|
13
|
+
XML 1.0 has no legal U+0000 character.
|
|
14
|
+
|
|
3
15
|
## [0.5.1] - 2026-06-22
|
|
4
16
|
|
|
5
17
|
### Changed
|
data/README.md
CHANGED
|
@@ -267,6 +267,22 @@ Detailed, test-backed notes live in `spec/conformance/README.md`.
|
|
|
267
267
|
* Type selectors are ASCII case-insensitive (CSS-correct for HTML; `LI` matches `<li>`)
|
|
268
268
|
* `Nokogiri::HTML5` is case-sensitive there.
|
|
269
269
|
|
|
270
|
+
### Text input (mutation APIs)
|
|
271
|
+
|
|
272
|
+
* Programmatic string arguments must be **valid UTF-8** (invalid bytes raise
|
|
273
|
+
`Makiri::Error`, never silently repaired - unlike HTML *parsing*, which decodes
|
|
274
|
+
leniently to U+FFFD).
|
|
275
|
+
* An embedded **NUL (U+0000)** is **accepted in HTML data-family content** -
|
|
276
|
+
text/comment node content (`create_text_node`, `create_comment`, `content=`)
|
|
277
|
+
and attribute values (`[]=`, `set_attribute_ns`) - and stored/read back
|
|
278
|
+
verbatim, matching the WHATWG DOM / browsers (`document.createTextNode("\0")`).
|
|
279
|
+
It is still **rejected** in names, tag names, namespaces, PI target/data, CSS
|
|
280
|
+
selectors, and XPath expressions/variable names (a NUL there raises).
|
|
281
|
+
* On re-parse, the HTML tokenizer replaces a U+0000 in text/attributes with
|
|
282
|
+
U+FFFD (WHATWG), so a serialized-then-reparsed round-trip is not byte-identical.
|
|
283
|
+
* `Makiri::XML` rejects NUL everywhere: XML 1.0 has no legal U+0000 character,
|
|
284
|
+
so admitting it would produce non-well-formed XML.
|
|
285
|
+
|
|
270
286
|
## Conformance
|
|
271
287
|
|
|
272
288
|
The XPath engine and XML parser are original code, so their correctness is held by
|
data/Rakefile
CHANGED
|
@@ -287,6 +287,12 @@ task :oom do
|
|
|
287
287
|
puts "(injection build left in place; run `rake clean compile` to restore a normal build)"
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
+
desc "CBMC proofs over the Ruby/Lexbor-free carve-out (core + XML + XPath front; " \
|
|
291
|
+
"needs cbmc; see docs/formal_verification.ja.md and verify/Makefile)"
|
|
292
|
+
task :verify do
|
|
293
|
+
sh "make -C verify smoke selftest cbmc"
|
|
294
|
+
end
|
|
295
|
+
|
|
290
296
|
desc "Run the performance benchmark (Makiri vs Nokogiri reference)"
|
|
291
297
|
task bench: :compile do
|
|
292
298
|
# Run outside the bundle so the bench-only gems (nokogiri, benchmark-ips)
|
data/lib/makiri/3.2/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/3.3/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/3.4/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/4.0/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/version.rb
CHANGED
data/script/check_c_safety.rb
CHANGED
|
@@ -83,6 +83,17 @@ RULES = [
|
|
|
83
83
|
# buffer unnoticed.
|
|
84
84
|
regex: /\(\s*mkr_verified_text_t\s*\)\s*\{|\bmkr_verified_text_t\s+\w+\s*=\s*\{/
|
|
85
85
|
),
|
|
86
|
+
Rule.new(
|
|
87
|
+
id: "borrowed_text_forge",
|
|
88
|
+
message: "mkr_borrowed_text_t must be built via mkr_borrowed_text()/mkr_borrowed_text_lit()/mkr_borrowed_text_from_*, not a raw struct literal",
|
|
89
|
+
# Same two forge shapes as verified_text_forge. Not a capability guard (a raw
|
|
90
|
+
# literal is semantically identical to the constructor); it funnels the
|
|
91
|
+
# "asserted-valid borrowed slice" through the single named constructor so the
|
|
92
|
+
# assertion stays auditable, matching mkr_text.h's stated intent ("replacing
|
|
93
|
+
# scattered casts"). core/** - where the constructors live - is globally
|
|
94
|
+
# ignore-pathed, so the definitions in mkr_text.h are exempt automatically.
|
|
95
|
+
regex: /\(\s*mkr_borrowed_text_t\s*\)\s*\{|\bmkr_borrowed_text_t\s+\w+\s*=\s*\{/
|
|
96
|
+
),
|
|
86
97
|
# --- HTML/XML representation boundary (see docs/html_xml_boundary_hardening) ---
|
|
87
98
|
# These symbols assume one DOM representation; using them outside their
|
|
88
99
|
# representation-correct / kind-checked home is how shared glue (XPath, NodeSet,
|
|
@@ -99,19 +99,16 @@ ignore_paths:
|
|
|
99
99
|
|
|
100
100
|
# --- parser-TU reader discipline (raw_scan_call / raw_cursor_member) ---
|
|
101
101
|
allowlist:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# --- verified_text_forge: the two Ruby-free test entry points. Neither can use
|
|
108
|
-
# mkr_verified_text_from_view (bridge = Ruby boundary), so each mints the
|
|
109
|
-
# token itself and must supply the contract by construction. ---
|
|
110
|
-
- path: ext/makiri/xpath/mkr_xpath_xml_selftest.c
|
|
111
|
-
rule: verified_text_forge
|
|
112
|
-
max: 2
|
|
113
|
-
reason: "Selftest expressions are compile-time string literals: NUL-terminated, NUL-free, valid UTF-8 by construction."
|
|
102
|
+
# --- verified_text_forge: the Ruby-free test entry points that mint over a
|
|
103
|
+
# runtime buffer (not a compile-time literal, which uses
|
|
104
|
+
# mkr_verified_text_lit). Neither can use mkr_verified_text_from_view
|
|
105
|
+
# (bridge = Ruby boundary), so each mints itself and must supply the
|
|
106
|
+
# contract by construction. ---
|
|
114
107
|
- path: ext/makiri/fuzz/xpath_fuzz.c
|
|
115
108
|
rule: verified_text_forge
|
|
116
109
|
max: 1
|
|
117
110
|
reason: "The libFuzzer harness mints over an owned mkr_strndup copy, which supplies NUL-termination and no-interior-NUL; UTF-8 validity is deliberately left to the lexer's strict decoder (its rejection path is fuzz-target behavior)."
|
|
111
|
+
- path: ext/makiri/fuzz/xml_xpath_fuzz.c
|
|
112
|
+
rule: verified_text_forge
|
|
113
|
+
max: 1
|
|
114
|
+
reason: "The expression mint is over an owned mkr_strndup copy, which supplies NUL-termination and no-interior-NUL; UTF-8 validity is pre-checked (the harness filters invalid UTF-8 before parsing). Mirrors xpath_fuzz.c."
|
data/suppressions/ruby.supp
CHANGED
|
@@ -125,6 +125,28 @@
|
|
|
125
125
|
fun:mark_roots
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
# ---- incremental GC write barrier: uninitialised-value reads ----
|
|
129
|
+
# An allocating Ruby C-API call from one of our cfuncs (e.g. rb_str_subseq in
|
|
130
|
+
# mkr_xml_decode_input) fires the incremental-GC write barrier synchronously;
|
|
131
|
+
# `gc_writebarrier_incremental` then reads an object's not-yet-written RVALUE
|
|
132
|
+
# flag word via `RVALUE_BLACK_P` - the same uninit-flag false positive as the GC
|
|
133
|
+
# drivers above, but reached through the barrier rather than a mark/sweep cycle,
|
|
134
|
+
# so the driver-anchored entries miss it. Our code never touches RVALUE flags;
|
|
135
|
+
# it only calls the allocating API, so anchoring on the barrier cannot mask a
|
|
136
|
+
# real uninit read of ours.
|
|
137
|
+
{
|
|
138
|
+
ruby-gc-cond-writebarrier_incremental
|
|
139
|
+
Memcheck:Cond
|
|
140
|
+
...
|
|
141
|
+
fun:gc_writebarrier_incremental
|
|
142
|
+
}
|
|
143
|
+
{
|
|
144
|
+
ruby-gc-value8-writebarrier_incremental
|
|
145
|
+
Memcheck:Value8
|
|
146
|
+
...
|
|
147
|
+
fun:gc_writebarrier_incremental
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
# ---- Ruby VM internal caches that are never freed before exit ----
|
|
129
151
|
# The global constant-cache / inline method-cache id_table is allocated lazily
|
|
130
152
|
# on method lookup and lives for the life of the VM, so RUBY_FREE_AT_EXIT still
|
data/verify/Makefile
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Verification build for the XML + XPath carve-out.
|
|
2
|
+
#
|
|
3
|
+
# Fully separate from the gem's extconf build: nothing here touches ext/'s
|
|
4
|
+
# Makefile or object files, and no Ruby VM or Lexbor library is linked (the
|
|
5
|
+
# only Lexbor pieces are UNMODIFIED vendor encoding sources - the vanilla
|
|
6
|
+
# constraint is about not patching, not about not compiling).
|
|
7
|
+
#
|
|
8
|
+
# Targets:
|
|
9
|
+
# make smoke - build every CBMC harness as a plain executable and run it
|
|
10
|
+
# (the link check: CBMC treats a missing function body as a
|
|
11
|
+
# nondet return, so only the linker catches a wrong source list)
|
|
12
|
+
# make selftest - build + run the carve-out selftest (XML parse + XPath eval,
|
|
13
|
+
# no Ruby, no Lexbor lib)
|
|
14
|
+
# make cbmc - the proven set (alloc/utf8/utf8-chain/utf8-words/span/
|
|
15
|
+
# spanbuf/buf/hash/xml-chars/xpath-number; ~8 min total)
|
|
16
|
+
# make cbmc-deep - the experimental set (xpath-lex/xpath-parse/xml-parse/
|
|
17
|
+
# xml-expand).
|
|
18
|
+
# These do NOT converge in practical time on current
|
|
19
|
+
# bit-level solvers (measured: >10 min each even at 3-4
|
|
20
|
+
# byte inputs, with MiniSat, kissat and Bitwuzla alike);
|
|
21
|
+
# kept as tuning knobs for future solver/harness work,
|
|
22
|
+
# e.g. make cbmc-xpath-parse XPATH_EXPR_MAX=4 XPATH_UNWIND=12
|
|
23
|
+
#
|
|
24
|
+
# Native libFuzzer targets live in ext/makiri/fuzz/ (their own Makefile + the
|
|
25
|
+
# nightly libfuzzer.yml workflow), not here - this directory is proofs only.
|
|
26
|
+
|
|
27
|
+
ROOT := ..
|
|
28
|
+
EXT := $(ROOT)/ext/makiri
|
|
29
|
+
LXB := $(ROOT)/vendor/lexbor/source
|
|
30
|
+
|
|
31
|
+
CC ?= cc
|
|
32
|
+
CBMC ?= cbmc
|
|
33
|
+
CFLAGS ?= -O1 -g -Wall -Wextra
|
|
34
|
+
# fmod & co. need an explicit -lm on Linux (folded into libc on macOS).
|
|
35
|
+
LDLIBS ?= -lm
|
|
36
|
+
INC := -I $(EXT) -I $(LXB) -I .
|
|
37
|
+
|
|
38
|
+
CORE_SRC := $(EXT)/core/mkr_alloc.c $(EXT)/core/mkr_buf.c \
|
|
39
|
+
$(EXT)/core/mkr_core.c $(EXT)/core/mkr_utf8.c
|
|
40
|
+
XML_SRC := $(EXT)/xml/mkr_xml_chars.c $(EXT)/xml/mkr_xml_index.c \
|
|
41
|
+
$(EXT)/xml/mkr_xml_mutate.c $(EXT)/xml/mkr_xml_node.c \
|
|
42
|
+
$(EXT)/xml/mkr_xml_tree.c
|
|
43
|
+
# Lexer/parser/number + shared support: what mkr_parse reaches.
|
|
44
|
+
XPATH_FRONT_SRC := $(EXT)/xpath/mkr_xpath_lex.c $(EXT)/xpath/mkr_xpath_parse.c \
|
|
45
|
+
$(EXT)/xpath/mkr_xpath_shared.c $(EXT)/xpath/mkr_xpath_number.c
|
|
46
|
+
# The full engine: dispatcher + the XML monomorphization (HTML side is stub.c).
|
|
47
|
+
XPATH_FULL_SRC := $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c \
|
|
48
|
+
$(EXT)/xpath/mkr_xpath_engine_xml.c
|
|
49
|
+
# Unmodified vendor sources for lxb_encoding_decode_valid_utf_8_single.
|
|
50
|
+
LXB_ENC_SRC := $(LXB)/lexbor/encoding/decode.c $(LXB)/lexbor/encoding/multi_res.c \
|
|
51
|
+
$(LXB)/lexbor/encoding/range_res.c $(LXB)/lexbor/encoding/single_res.c
|
|
52
|
+
|
|
53
|
+
ENGINE_SRC := $(CORE_SRC) $(XML_SRC) $(XPATH_FULL_SRC) $(LXB_ENC_SRC) stub.c
|
|
54
|
+
|
|
55
|
+
BUILD := build
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------- smoke + selftest
|
|
58
|
+
|
|
59
|
+
HARNESSES := alloc utf8 utf8-chain utf8-words span spanbuf buf hash xml-chars xml-expand xpath-number xpath-lex xpath-parse xml-parse
|
|
60
|
+
|
|
61
|
+
smoke: $(HARNESSES:%=$(BUILD)/harness-%)
|
|
62
|
+
@for h in $^; do echo "== $$h"; $$h || exit 1; done
|
|
63
|
+
@echo "smoke: all harnesses link and run"
|
|
64
|
+
|
|
65
|
+
$(BUILD)/harness-alloc: harness_alloc.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
66
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
67
|
+
$(BUILD)/harness-utf8: harness_utf8.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
68
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
69
|
+
$(BUILD)/harness-utf8-chain: harness_utf8_chain.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
70
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
71
|
+
$(BUILD)/harness-utf8-words: harness_utf8_words.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
72
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
73
|
+
$(BUILD)/harness-span: harness_span.c | $(BUILD)
|
|
74
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
75
|
+
$(BUILD)/harness-spanbuf: harness_spanbuf.c | $(BUILD)
|
|
76
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
77
|
+
$(BUILD)/harness-buf: harness_buf.c $(EXT)/core/mkr_buf.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
78
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
79
|
+
$(BUILD)/harness-hash: harness_hash.c | $(BUILD)
|
|
80
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
81
|
+
$(BUILD)/harness-xpath-number: harness_xpath_number.c $(EXT)/xpath/mkr_xpath_number.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
82
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
83
|
+
# mkr_parse's limit checks + limits_init_defaults live in mkr_xpath.c (the
|
|
84
|
+
# dispatcher), which references both engine instances - so the smoke link
|
|
85
|
+
# takes the full engine (stub.c supplies the HTML side).
|
|
86
|
+
$(BUILD)/harness-xpath-parse: harness_xpath_parse.c $(ENGINE_SRC) | $(BUILD)
|
|
87
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
88
|
+
$(BUILD)/harness-xpath-lex: harness_xpath_lex.c $(ENGINE_SRC) | $(BUILD)
|
|
89
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
90
|
+
$(BUILD)/harness-xml-parse: harness_xml_parse.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
91
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
92
|
+
$(BUILD)/harness-xml-chars: harness_xml_chars.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
93
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
94
|
+
$(BUILD)/harness-xml-expand: harness_xml_expand.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
95
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
96
|
+
|
|
97
|
+
selftest: $(BUILD)/selftest
|
|
98
|
+
$(BUILD)/selftest
|
|
99
|
+
|
|
100
|
+
$(BUILD)/selftest: selftest_main.c $(ENGINE_SRC) $(EXT)/xpath/mkr_xpath_xml_selftest.c | $(BUILD)
|
|
101
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
102
|
+
|
|
103
|
+
$(BUILD):
|
|
104
|
+
mkdir -p $(BUILD)
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------- CBMC proofs
|
|
107
|
+
#
|
|
108
|
+
# Every proof gets --bounds-check --pointer-check (memory safety) and
|
|
109
|
+
# --signed-overflow-check (UB); --unsigned-overflow-check is added ONLY where
|
|
110
|
+
# wrapping would be a bug (the size arithmetic) - the UTF-8 validator's
|
|
111
|
+
# word-at-a-time scan and hash-style code wrap on purpose, and unsigned wrap
|
|
112
|
+
# is defined C. --malloc-may-fail --malloc-fail-null verifies the fail-closed
|
|
113
|
+
# OOM branches. Unwind bounds are per-harness knobs; --unwinding-assertions
|
|
114
|
+
# turns "too small a bound" into a failure instead of a silent partial proof.
|
|
115
|
+
|
|
116
|
+
CBMC_MEM := --bounds-check --pointer-check --signed-overflow-check \
|
|
117
|
+
--div-by-zero-check --unwinding-assertions --object-bits 12
|
|
118
|
+
CBMC_OOM := --malloc-may-fail --malloc-fail-null
|
|
119
|
+
# libc bodies CBMC lacks on this platform (see the file's comment).
|
|
120
|
+
CBMC_MODELS := cbmc_models.c
|
|
121
|
+
|
|
122
|
+
# mkr_grow_capacity doubles from 8, so <= ~60 iterations before the cap check.
|
|
123
|
+
ALLOC_UNWIND ?= 64
|
|
124
|
+
cbmc-alloc:
|
|
125
|
+
$(CBMC) harness_alloc.c $(CBMC_MODELS) $(EXT)/core/mkr_alloc.c $(INC) \
|
|
126
|
+
$(CBMC_MEM) $(CBMC_OOM) --unsigned-overflow-check --conversion-check \
|
|
127
|
+
--unwind $(ALLOC_UNWIND)
|
|
128
|
+
|
|
129
|
+
UTF8_UNWIND ?= 12
|
|
130
|
+
cbmc-utf8:
|
|
131
|
+
$(CBMC) harness_utf8.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
132
|
+
$(CBMC_MEM) --unwind $(UTF8_UNWIND)
|
|
133
|
+
|
|
134
|
+
# Chain length and unwind grow together (unwind must exceed the byte count;
|
|
135
|
+
# see the harness header for the measured length/time trade-off).
|
|
136
|
+
UTF8_CHAIN_MAX ?= 12
|
|
137
|
+
UTF8_CHAIN_UNWIND ?= 16
|
|
138
|
+
cbmc-utf8-chain:
|
|
139
|
+
$(CBMC) harness_utf8_chain.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
140
|
+
-DVERIFY_UTF8_CHAIN_MAX=$(UTF8_CHAIN_MAX) \
|
|
141
|
+
$(CBMC_MEM) --unwind $(UTF8_CHAIN_UNWIND)
|
|
142
|
+
|
|
143
|
+
cbmc-utf8-words:
|
|
144
|
+
$(CBMC) harness_utf8_words.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
145
|
+
$(CBMC_MEM) --unwind 20
|
|
146
|
+
|
|
147
|
+
SPAN_UNWIND ?= 12
|
|
148
|
+
cbmc-span:
|
|
149
|
+
$(CBMC) harness_span.c $(CBMC_MODELS) $(INC) \
|
|
150
|
+
$(CBMC_MEM) --unwind $(SPAN_UNWIND)
|
|
151
|
+
|
|
152
|
+
cbmc-spanbuf:
|
|
153
|
+
$(CBMC) harness_spanbuf.c $(CBMC_MODELS) $(INC) \
|
|
154
|
+
$(CBMC_MEM) --unwind $(SPAN_UNWIND)
|
|
155
|
+
|
|
156
|
+
cbmc-buf:
|
|
157
|
+
$(CBMC) harness_buf.c $(CBMC_MODELS) $(EXT)/core/mkr_buf.c $(EXT)/core/mkr_alloc.c $(INC) \
|
|
158
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind 16
|
|
159
|
+
|
|
160
|
+
# The sizer only shifts, so unlike the mul/div guards this closes at full
|
|
161
|
+
# width; the loop runs at most one step per size_t bit.
|
|
162
|
+
HASH_UNWIND ?= 66
|
|
163
|
+
cbmc-hash:
|
|
164
|
+
$(CBMC) harness_hash.c $(CBMC_MODELS) $(INC) \
|
|
165
|
+
$(CBMC_MEM) --unwind $(HASH_UNWIND)
|
|
166
|
+
|
|
167
|
+
# XML character classes: predicate inclusions over all code points + the
|
|
168
|
+
# validators cross-checked against the independent UTF-8 validator. CBMC only
|
|
169
|
+
# needs mkr_xml_chars.c + mkr_utf8.c (the other xml/ entries the header
|
|
170
|
+
# declares stay bodyless - unreachable from this harness).
|
|
171
|
+
XML_CHARS_UNWIND ?= 12
|
|
172
|
+
cbmc-xml-chars:
|
|
173
|
+
$(CBMC) harness_xml_chars.c $(CBMC_MODELS) $(EXT)/xml/mkr_xml_chars.c $(EXT)/core/mkr_utf8.c $(INC) \
|
|
174
|
+
$(CBMC_MEM) --unwind $(XML_CHARS_UNWIND)
|
|
175
|
+
|
|
176
|
+
# The XPath Number scanner/converter. The cbmc_models.c strtod ("conversion
|
|
177
|
+
# unavailable") steers from_extent down its own fallback chain - the isolating
|
|
178
|
+
# reparse and the hand assembly, i.e. exactly our code - on every input; the
|
|
179
|
+
# libc-success path is trusted, not verified (like memcpy).
|
|
180
|
+
cbmc-xpath-number:
|
|
181
|
+
$(CBMC) harness_xpath_number.c $(CBMC_MODELS) $(EXT)/xpath/mkr_xpath_number.c $(EXT)/core/mkr_alloc.c $(INC) \
|
|
182
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind 12
|
|
183
|
+
|
|
184
|
+
cbmc: cbmc-alloc cbmc-utf8 cbmc-utf8-chain cbmc-utf8-words cbmc-span cbmc-spanbuf cbmc-buf cbmc-hash cbmc-xml-chars cbmc-xpath-number
|
|
185
|
+
@echo "cbmc: all proofs passed"
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------- experimental (cbmc-deep)
|
|
188
|
+
#
|
|
189
|
+
# The whole-entry parser/lexer proofs below are formulated and smoke-checked,
|
|
190
|
+
# but no current solver closes them in practical time (see the header). CBMC
|
|
191
|
+
# gets mkr_xpath.c for the limit checks but NOT the engine instances or the
|
|
192
|
+
# vendor encoding tables (nothing there is reachable from mkr_parse; the eval
|
|
193
|
+
# entries mkr_xpath.c references stay bodyless - unreachable from these
|
|
194
|
+
# harnesses). Expression/input length and unwind grow together.
|
|
195
|
+
|
|
196
|
+
XPATH_EXPR_MAX ?= 6
|
|
197
|
+
XPATH_UNWIND ?= 16
|
|
198
|
+
cbmc-xpath-lex:
|
|
199
|
+
$(CBMC) harness_xpath_lex.c $(CBMC_MODELS) $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c $(CORE_SRC) $(INC) \
|
|
200
|
+
-DVERIFY_XPATH_EXPR_MAX=$(XPATH_EXPR_MAX) \
|
|
201
|
+
$(CBMC_MEM) --unwind $(XPATH_UNWIND)
|
|
202
|
+
|
|
203
|
+
cbmc-xpath-parse:
|
|
204
|
+
$(CBMC) harness_xpath_parse.c $(CBMC_MODELS) $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c $(CORE_SRC) $(INC) \
|
|
205
|
+
-DVERIFY_XPATH_EXPR_MAX=$(XPATH_EXPR_MAX) \
|
|
206
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind $(XPATH_UNWIND)
|
|
207
|
+
|
|
208
|
+
XML_INPUT_MAX ?= 6
|
|
209
|
+
XML_UNWIND ?= 16
|
|
210
|
+
cbmc-xml-parse:
|
|
211
|
+
$(CBMC) harness_xml_parse.c $(CBMC_MODELS) $(XML_SRC) $(CORE_SRC) $(INC) \
|
|
212
|
+
-DVERIFY_XML_INPUT_MAX=$(XML_INPUT_MAX) \
|
|
213
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind $(XML_UNWIND)
|
|
214
|
+
|
|
215
|
+
# Entity/char-ref expansion over nondet valid-UTF-8 input (the document it
|
|
216
|
+
# writes into is parsed from a concrete 4-byte input, so only the expansion
|
|
217
|
+
# input is nondet). Does not converge: 7-byte input, --unwind 12, still in
|
|
218
|
+
# BMC when the 420 s alarm killed it (measured; higher unwinds are strictly
|
|
219
|
+
# larger). No $(CBMC_OOM): a NULL doc is treated as early-exit, OOM injection
|
|
220
|
+
# is not what this harness is about.
|
|
221
|
+
# Also measured: 5-byte input still did not converge in 6 min - the cost is
|
|
222
|
+
# the whole-parser+arena state behind the doc handle, not the input length.
|
|
223
|
+
XML_EXPAND_MAX ?= 7
|
|
224
|
+
XML_EXPAND_UNWIND ?= 12
|
|
225
|
+
cbmc-xml-expand:
|
|
226
|
+
$(CBMC) harness_xml_expand.c $(CBMC_MODELS) $(XML_SRC) $(CORE_SRC) $(INC) \
|
|
227
|
+
-DVERIFY_XML_EXPAND_MAX=$(XML_EXPAND_MAX) \
|
|
228
|
+
$(CBMC_MEM) --unwind $(XML_EXPAND_UNWIND)
|
|
229
|
+
|
|
230
|
+
cbmc-deep: cbmc-xpath-lex cbmc-xpath-parse cbmc-xml-parse cbmc-xml-expand
|
|
231
|
+
|
|
232
|
+
clean:
|
|
233
|
+
rm -rf $(BUILD)
|
|
234
|
+
|
|
235
|
+
.PHONY: smoke selftest cbmc cbmc-alloc cbmc-utf8 cbmc-utf8-chain cbmc-utf8-words cbmc-span \
|
|
236
|
+
cbmc-spanbuf cbmc-buf cbmc-hash cbmc-xml-chars cbmc-xpath-number \
|
|
237
|
+
cbmc-deep cbmc-xpath-lex cbmc-xpath-parse cbmc-xml-parse cbmc-xml-expand clean
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Plain-C bodies for libc functions CBMC's built-in library does not model on
|
|
2
|
+
* this platform (a bodyless callee havocs its return value, turning every
|
|
3
|
+
* downstream pointer check into a false failure). Compiled ONLY into the CBMC
|
|
4
|
+
* runs, never into the smoke/selftest/fuzzer builds (those link the real libc).
|
|
5
|
+
* Each body is the obvious reference implementation, so proving against it is
|
|
6
|
+
* proving against the function's contract. */
|
|
7
|
+
#ifdef __CPROVER__
|
|
8
|
+
|
|
9
|
+
#include <stddef.h>
|
|
10
|
+
#include <stdlib.h> /* the SDK's strtod declaration carries an asm alias
|
|
11
|
+
* (_strtod on macOS); defining without it leaves the
|
|
12
|
+
* callee bodyless under its aliased name */
|
|
13
|
+
#include <string.h>
|
|
14
|
+
|
|
15
|
+
void *
|
|
16
|
+
memchr(const void *s, int c, size_t n)
|
|
17
|
+
{
|
|
18
|
+
const unsigned char *p = s;
|
|
19
|
+
for (size_t i = 0; i < n; ++i) {
|
|
20
|
+
if (p[i] == (unsigned char)c) return (void *)(p + i);
|
|
21
|
+
}
|
|
22
|
+
return NULL;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* strtod, modeled as "libc conversion unavailable" (no bytes consumed). This
|
|
26
|
+
* is a deliberate choice, not a shortcut: correctly-rounded decimal parsing is
|
|
27
|
+
* libc territory we trust rather than verify (like memcpy), and this model
|
|
28
|
+
* steers every caller down its own fallback - for mkr_xpath_number_from_extent
|
|
29
|
+
* that means the isolating-reparse and hand-assembly paths, i.e. exactly OUR
|
|
30
|
+
* code, get proven on all inputs. */
|
|
31
|
+
double
|
|
32
|
+
strtod(const char *nptr, char **endptr)
|
|
33
|
+
{
|
|
34
|
+
if (endptr != NULL) *endptr = (char *)nptr;
|
|
35
|
+
return 0.0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#endif /* __CPROVER__ */
|