Skip to content

v0.1.9

Choose a tag to compare

@github-actions github-actions released this 28 Oct 19:06
· 15 commits to main since this release
90a7008

Fixed

  • SMB2 credit grants now match smbd 4.19.5 (smb2.credits 0/3 → 3/3,
    ROADMAP R-05).
    Three defects, found by capturing Samba 4.19.5's
    per-response grants with smb2_credits:5 debug logging and diffing
    against rssmb's (extracted from source3/smbd/smb2_server.c
    smb2_set_operation_credit):
    1. max_credits default 1024 → 8192 — smbd's smb2 max credits
      default; the torture subtests assert ≥ 8192 grants after session
      setup. All tests/live/*.toml configs follow.
    2. Per-response grant policy was "drain the window" instead of
      "refund the charge, grant up to the window budget."
      New
      CreditManager::grant_for_response(charge, requested, interim, charge_only): granted = charge + min(max(requested,1)−1, max_grant − charge), clamped by the remaining window budget
      (max_grant − (high − low)) — exactly smbd's algorithm. Interim
      (async) responses grant 0; NEGPROT and non-auth-complete
      SESSION_SETUP responses refund the charge only (additional_max = 0, smbd's SMB2_OP_NEGPROT/failed-SMB2_OP_SESSSETUP cases).
      The old grant() capped by outstanding balance only, so the client
      balance could never converge to the window while requests were
      in flight.
    3. Zero-charge requests no longer reuse their MessageId. smbd
      charges MAX(CreditCharge, 1) to the MessageId window too
      (smb2_validate_message_id); the old zero-charge workaround
      (window consumed 0, MessageId reusable) pinned low behind
      phantom holes on every negotiate (the first request is charge 0),
      permanently eating window budget — the off-by-4/8188 seen in the
      torture. A charge of 0 now consumes its MessageId and advances
      low like a charge of 1.
      This supersedes the 0.1.4 credit-window entry (PR #31), whose
      root-cause story — "impacket sends WRITE with credit_charge=0 and
      then CLOSE with the same MessageId" — is not what impacket does on
      the wire: impacket computes WRITE charges as
      1 + (len-1)//65536 (always ≥ 1, smb3.py) and advances its
      SequenceWindow on every send, so its MessageIds are strictly
      sequential and never reused. The client-side step-back that PR #31
      observed is driven by the response echo
      (SequenceWindow += CreditCharge - 1, smb3.py recvSMB) and only
      fires before the dialect is negotiated, where it is exempt; #31's
      real fix value was the balance-availability check, which is kept
      (in check_and_deduct, available < max(charge,1) still rejects).
      The PR #31 repro (tests/conformance/poc-impacket-test.py, incl.
      smb.write() → smb.close() → logoff() on one connection) passes
      against this change (3/3), as do all 20 live scenarios.
      This entry corrects the root-cause description given in 0.1.4.
      Golden vs rssmb, full login sequence (CreditRequest 65535):
      smbd grants [NEGPROT 1, SETUP-interim 1, SETUP-auth 8192] → client
      cur_credits 8192; rssmb now byte-identical. All 19 live interop
      scenarios (impacket/smbclient-style, incl. bigwrite, rename,
      r30_reopen, r11_eof) still pass; smb2.read delta unchanged.

Internal

  • Lock audit complete (ROADMAP R-06). All previously uninspected
    std-guard sites verified — none holds a std::sync guard across an
    .await (ioctl.rs guards a tokio mutex; quic.rs:650 is a test-only
    RAII admission; connection.rs:650 has no lock; connection.rs:1880 is
    a test struct literal). Dual-lock ServerState pairs resolved:
    dead identities boot Arc removed (all auth paths use the
    live-reload identities_snapshot()), dead add_session removed,
    and the canonical session-table order (registry → sessions →
    retired_sessions, never overlapping) documented in
    docs/lock-audit.md along with the intentional std/tokio flavor
    split for signing/encryption vs trees/opens. The audit also records
    known follow-ups found during it: the global sessions Vec is
    never populated (multichannel bind-by-lookup inert — pre-existing),
    reload()'s two-lock swap window, session-table teardown
    non-atomicity, and the unbounded retired_sessions set.
  • CI: clippy::await_holding_lock now passed explicitly (-D) in
    both workflows, alongside -Dclippy::all — the lint was already
    covered by clippy::all (suspicious group); the explicit flag
    documents the await-safety invariant. 0 fires across the workspace
    (verified on clippy 0.1.99 / rustc 1.99.1).
  • docker: builder image pinned rust:1.90 → rust:1.93-bookworm — the
    lockfile's sspi 0.23 requires rustc ≥ 1.93, so the pinned 1.90 builder
    image can no longer compile the workspace.
  • MSRV raised to 1.93 and made real. rust-version in Cargo.toml
    1.85 → 1.93 (the actual floor, driven by sspi 0.23; 1.85 was stale and
    unbuildable), README badge updated, and a new msrv CI job runs
    cargo check --workspace --locked on the 1.93 toolchain so the
    declared floor can't silently rot again.