Skip to content

Replace cl-isaac with ironclad for connection ID generation - #458

Open
bhw-foss wants to merge 1 commit into
rabbibotton:mainfrom
bhw-foss:deprecate-cl-isaac-for-ironclad
Open

Replace cl-isaac with ironclad for connection ID generation#458
bhw-foss wants to merge 1 commit into
rabbibotton:mainfrom
bhw-foss:deprecate-cl-isaac-for-ironclad

Conversation

@bhw-foss

Copy link
Copy Markdown

I ran into this issue with sbcl declaim safety 2 and patched my project by overriding clog-connection::random-hex-string. Claude's diagnosis is below.

The bug

cl-isaac's rand32 unconditionally decfs its randcnt slot
(declared (unsigned-byte 32)) before checking whether the
256-word ISAAC block is exhausted:

(defun rand32 (ctx)
  (let ((c (isaac-ctx-randcnt ctx)))
    (decf (isaac-ctx-randcnt ctx))       ; <- unconditional, underflows 0 -> -1
    (if (zerop c)
        (progn (generate-next-isaac-block ctx)
               (setf (isaac-ctx-randcnt ctx) 255)
               (aref (isaac-ctx-randrsl ctx) 255))
        (aref (isaac-ctx-randrsl ctx) (isaac-ctx-randcnt ctx)))))

cl-isaac compiles its own isaac-32.lisp under (safety 2) (set in
make.lisp), so SBCL's typed-slot writer raises TYPE-ERROR on the
underflow, unwinding rand32 before the (if (zerop c) ...) reset
branch ever runs. randcnt stays at 0 and the PRNG is permanently
wedged — every subsequent call traps in the same place.

How it manifests in CLOG

clog-connection::random-hex-string throws inside
handle-new-connection. The surrounding handler-case swallows the
error, the WebSocket upgrade completes, but the
(websocket-driver:send connection "clog['connection_id']='…'") and
the on-connect thread spawn never run — so the server sends 0 bytes
after the 101 handshake and the browser sits on an unbootstrapped body
forever. Restarting "fixes" it briefly because *isaac-ctx* is
restored from the save-lisp-and-die image with a non-zero randcnt;
after one ISAAC block (~256 connections) it wedges again.

Upstream is unresponsive — issue and PR open since 2023:

The fix

CLOG already called ironclad:byte-array-to-hex-string +
ironclad:random-data on the Windows branch of random-hex-string,
and cl-pass (already a direct dep) pulls ironclad in transitively.
This PR:

  • drops cl-isaac from clog.asd and declares ironclad explicitly,
  • removes the *isaac-ctx* defparameter (not exported — verified
    against the defsection @clog-connection export list),
  • collapses random-hex-string to a single platform-agnostic body
    using the existing ironclad codepath.

Output contract is preserved: 32 lowercase hex chars (16 octets from
ironclad's default OS PRNG — /dev/urandom on Unix,
CryptGenRandom on Windows).

Verification

Loaded :clog clean in SBCL 2.5.2, then:

(every (lambda (s) (= 32 (length s)))
       (loop repeat 10000 collect (clog-connection:random-hex-string)))
;; => T

— would have wedged the old cl-isaac path after ~256 calls under
(safety 2).

cl-isaac's rand32 unconditionally decrements its (unsigned-byte 32)
randcnt slot before checking for block exhaustion. Under (safety 2)
the type check fires on the 0 -> -1 underflow, unwinds before the
reset branch runs, and permanently wedges the PRNG -- causing
clog-connection::random-hex-string to throw inside
handle-new-connection and silently break connection bootstrap on
long-running servers (upstream issue rabbibotton#12, PR rabbibotton#13 unmerged).

cl-pass already pulls ironclad in transitively, and the Windows
branch of random-hex-string already used ironclad. Drop cl-isaac
entirely, declare ironclad explicitly, and collapse the platform
conditional.
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