Skip to content

Vendor bitn v0.6.1 from the release artifact - #6

Merged
derek-miller merged 1 commit into
mainfrom
vendor-bitn-v0.6.1
Aug 8, 2026
Merged

Vendor bitn v0.6.1 from the release artifact#6
derek-miller merged 1 commit into
mainfrom
vendor-bitn-v0.6.1

Conversation

@derek-miller

Copy link
Copy Markdown
Contributor

Vendors bitn v0.6.1, taken from the GitHub release asset rather than a local make build.

sha256  684ba2c5ab905f5aaceff9fdd4512eed5c327926ca13730dec3c34760cbd9dae

The bump carries a real correctness fix, not just a version string. _compat.lua selected its native-operator implementation by testing whether a & b parses. LuaJIT rolling releases from 2026 accept that syntax but return a signed 32-bit result from the bit library, so the branch was taken on a runtime whose values need the to_unsigned() normalisation that branch skips. arshift gave plainly wrong answers on such a host, not merely sign-extended ones: arshift(0x80000000, 1) returned 0x40000000 instead of 0xC0000000.

v0.6.1 tests the value rather than the runtime, so it stays correct for whatever grows the syntax next.

Control4 controllers run LuaJIT (verified on a dev controller: jit is present, 2.1.1700206165), so this is not hypothetical for us. That particular build predates the syntax change, which is the only reason we had no exposure yet.

Full test suite passes against the bumped vendor.

Picks up the _compat.lua implementation-selection fix: the native-operator
branch was chosen by testing whether `a & b` parses, which LuaJIT rolling
releases from 2026 accept while returning signed 32-bit values from the bit
library. That skipped the to_unsigned() normalisation those hosts need and
made arshift return wrong values, not just differently-signed ones.

Artifact taken from the v0.6.1 release, sha256
684ba2c5ab905f5aaceff9fdd4512eed5c327926ca13730dec3c34760cbd9dae.

Full suite passes: 15/15 modules, including the 27 OpenSSL feature-gating
tests.

@svc-finitelabs svc-finitelabs Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified the vendor bump end to end, including building a LuaJIT with the backported bit operators to reproduce the bug and confirm the fix. Approving.

Provenance: byte-exact

vendor/bitn.lua at 099b705 is byte-identical to the bitn.lua asset on the finitelabs/lua-bitn v0.6.1 release (asset 506709615, 108625 bytes), and its sha256 matches the one in the PR body exactly:

684ba2c5ab905f5aaceff9fdd4512eed5c327926ca13730dec3c34760cbd9dae

diff against the downloaded asset is empty, so nothing in the diff is hand-edited. The three non-version hunks (value probe, fresh bit_ok/bit_module locals, the local expected narrowing in selftest) all came from upstream.

The premise is real, and I can date it precisely

LuaJIT backported the v3.0 syntax extensions to v2.1 in a2ce8114 on 2026-07-20, including the bit operators ~ & | ~ << >> ~>>. Upstream doc/extensions.html pins the cutoff:

Bytecode that uses a bit operator can only be loaded by LuaJIT 2.1.1784535649 or higher.

The presence of a separate ~>> arithmetic-shift operator is itself the tell that these are 32-bit bit-library semantics rather than 5.3 integer semantics.

I built v2.1 HEAD (1edc3e5, LuaJIT 2.1.1785763465) and measured it:

a & b  (0xFFFFFFFF, 0xFFFFFFFF) = -1        -- identical to bit.band
0x80000000 >> 1                  = 1073741824
OLD probe (fn ~= nil)  selects native: true   <-- the bug
NEW probe (value check)              : false  <-- correct

Then ran both vendored revisions on that runtime:

v0.6.0 (base) v0.6.1 (head)
arshift(0x80000000, 1) 0x40000000 0xC0000000
band(0xFFFFFFFF, 0xFFFFFFFF) -1 0xFFFFFFFF
bnot(0) -1 0xFFFFFFFF
bit32.selftest() / bit64.selftest() fails passes

So the 0x40000000 figure in the description reproduces exactly. The mechanism is worth spelling out: _compat.arshift masks with native_band(a, MASK32), which on this host yields -2147483648, so the a >= 0x80000000 sign test is false and the sign extension is skipped entirely.

Control4's 2.1.1700206165 is dated 2023-11-17, well below the 1784535649 cutoff, so the controller reading is right.

One correction: this is not merely forward-looking

The description says the controller build predating the syntax change is "the only reason we had no exposure yet." That understates it. CI on main at ce3e452 installed LuaJIT 2.1.1785763465 (job 93147638691), which is past the cutoff, and passed green while shipping the broken v0.6.0. The bug is live on a runtime the project already tests against today.

The reason nothing went red: src/crypto/ calls almost exclusively the raw_* entry points (raw_bxor, raw_band, raw_rshift, raw_add, raw_ror, raw_bor, raw_lshift, raw_bnot, raw_rol), and the signed 32-bit representation stays self-consistent through chained xor/and/or, with raw_rshift re-masking before shifting and the final byte extraction landing on correct values. The wrapped ops that are visibly wrong are the ones lua-crypto never calls. I confirmed this directly: the full suite is 15/15 on 2.1.1785763465 at both base and head.

That means "full test suite passes" is true but carries no signal here, in either direction. It is not evidence the bug was absent, and it will not be evidence if this regresses again.

Also, the impact is broader than arshift. On v0.6.0 the bit32 selftest fails on mask, band, bor and more, all returning sign-extended negatives. arshift is just the one that is wrong in a way a mask cannot launder.

Suggested follow-up (not blocking)

The CI matrix already floats luajit-2.1 to the latest rolling build, so it lands on affected runtimes automatically, but it cannot see this class of breakage. Gating bitn.bit32.selftest() and bitn.bit64.selftest() in the build would have caught it on main before this PR existed, and it is a couple of lines. Worth a ticket.

On the change itself

The value probe is the right shape. Testing fn(0xFFFFFFFF, 0xFFFFFFFF) == 0xFFFFFFFF compares a literal against a computed value in the same numeric domain, so it needs no runtime allowlist, and falling through to the bit library path is the conservative direction. Verified no regression on genuine 5.3+ semantics: Lua 5.5.0 answers 4294967295 and still selects the native branch.

The bit_ok/bit_module rename is a real hygiene fix rather than cosmetics, since the old code reassigned the same ok/result locals used by the native probe. I checked that nothing reads those locals after line 49, so there is no behaviour change, only the removal of a trap. The local expected hunk is a pure narrowing refactor with test unmutated in the loop.

What I did not verify

  • No hardware controller run. The Control4 claim rests on the version number in the description plus the upstream cutoff, both of which check out, but I did not query a controller myself.
  • I did not audit the rest of the 108KB vendored file beyond the diff, on the basis that it is byte-identical to a signed release artifact.
  • CI facts above are cited by immutable job ID; I made no claim about current check state.

@derek-miller
derek-miller merged commit 4c17f4a into main Aug 8, 2026
11 checks passed
@derek-miller
derek-miller deleted the vendor-bitn-v0.6.1 branch August 8, 2026 23:13
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.

1 participant