Skip to content

Releases

v0.1.10

Choose a tag to compare

@github-actions github-actions released this 30 Oct 21:19
f3b82a7

Changed

  • R-30 closed as cannot-reproduce; repro pinned as the r30_reopen live
    scenario (#49).
    The reported symptom (READ answering an empty body with
    STATUS_OK after CREATE/WRITE/CLOSE + re-open) does not reproduce on the
    claimed-failing commit d791d4d nor on main (dialects 2.1/3.0/3.1.1,
    impacket 0.13.1); the exact sequence is committed as tests/live scenario
    r30_reopen so it can't silently regress. Past-EOF READ semantics
    tracked under R-11.
  • EOF READ semantics pinned as the r11_eof live scenario with the Samba
    4.19.5 golden (#50).
    rssmb already matches smbd byte-for-byte on all six
    probed sequences (READ at/past EOF with length > 0 →
    STATUS_END_OF_FILE + 0 B; a window crossing EOF → STATUS_OK +
    partial; length 0 at any offset → STATUS_OK + 0 B), and on the
    shrink-while-open probe; the golden capture (tests/live/golden-r11-eof.json)
    and the scenario keep it that way.
  • WORKING-METHOD rule 11: evidence discipline. A roadmap entry claiming
    a bug must carry a committed repro script or a captured log ("observed
    once in a sandbox" is not evidence); every result row in a report must
    come from a command run in that session, anything else marked [inferred].

Performance

  • R-07a: mimalloc is the global allocator for release/Docker builds
    (musl), feature-gated and on by default.
    Identical-build A/B
    (kernel CIFS mount 3.1.1, fio r16 bs=1M direct, 30 s ×3 per cell,
    1000-file create/list/delete loop ×3 in a fresh tree; medians,
    tests/perf/build-ab.sh reproduces all three binaries with identical
    flags): unsealed read bandwidth 378 → 2480 MiB/s (6.6×, ≈1.9× the
    glibc baseline of 1262), sealed 327 → 942 MiB/s (2.9×, ≈1.7× glibc's
    523); small-file loop: list 31 → 16 ms, create 560 → 455 ms,
    delete ≈2.1 s both. Idle RSS +8 MB (43 → 51 MB), binary size +6%.
    Attribution of the remaining musl/glibc gap: not profiled in-sandbox.
    Data integrity verified (fio verify=crc32c 2 GiB write+read-back
    sealed and unsealed, 2 GiB random file sha256 round-trip through the
    mount); full uncapped smbtorture baseline on the mimalloc binary is
    identical to tests/conformance/results.md (0 main→mimalloc delta,
    same harness invocation, same day). An earlier session's
    AES-128-GMAC signing-negotiation and lock-21 claims are retracted as
    counting artifacts of the old harness formula.

Fixed

  • smbtorture harness: a client panic or nonzero exit code now FAILs
    the suite, never PASSes (#56).
    smbtorture can die mid-suite (e.g.
    SIGSEGV) having printed zero failure: lines, and the old tally
    (total - fail) then counted every started subtest as passed. Both
    run-smbtorture.sh and run-smbtorture-poc.sh now fail the suite
    on any panic marker (smb_panic|PANIC|Segmentation fault) in the
    log — the tally is suppressed as unreliable — and on any nonzero
    exit status (not just the timeout cases). Known client-side trigger:
    smbtorture 4.19.5 SIGSEGVs on smb2.compound.related1 when the
    server negotiates no cipher (smb2cli_session_set_id_and_flags
    NULL deref) — reproduced identically against unmodified smbd
    4.19.5 with smb encrypt = off; run the test server with
    encryption = "desired".

v0.1.9

Choose a tag to compare

@github-actions github-actions released this 28 Oct 19:06
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.

v0.1.8

Choose a tag to compare

@github-actions github-actions released this 25 Oct 22:54
d791d4d

Changed

  • Dependency refresh (24 bumps, no behavior change). All
    merge-blocking dependency updates since 0.1.7, each green through the
    full gate (fmt, clippy -D warnings, build --locked, test --workspace,
    deny, audit, and the impacket/smbclient/rclone/publish-on-close
    interop jobs):
    • aes 0.8.4 → 0.9.3 with companion bumps aes-gcm 0.10 → 0.11 and
      cmac 0.7 → 0.8. AES-128-GCM encrypt/decrypt and CMAC signing
      adapted to the new aead API surface (encrypt/decrypt_inout_detached,
      Array conversions via TryFrom/Into); all 35 crypto unit tests
      pass unchanged, wire behavior byte-identical.
    • rand 0.8.8 → 0.10.3 (rng(), Rng, random_range, SysRng +
      UnwrapErr API adaptation — affects negotiate-context nonce
      generation only).
    • cap-std/cap-primitives 3.4.6 → 4.0.3 (family kept on one
      version to avoid dual FollowSymlinks types in the graph).
    • sspi 0.21.3 → 0.23.0, thiserror 1.0.69 → 2.0.21,
      libc 0.2.190 → 0.2.191, unicode-normalization 0.1.25 → 0.1.26,
      md4 0.10.6 → 0.11.0.
    • GitHub Actions: checkout v7, download-artifact v8,
      build-push-action v7, metadata-action v6, gh-release v3.
  • Docs: tracked roadmap and working method. docs/ROADMAP.md
    (R-01…R-29, P0–P3 + accepted-never, the single tracked list of open
    work; every campaign references an item ID) and
    docs/WORKING-METHOD.md (the 10 working rules). README and
    tests/conformance/results.md now point at the roadmap; PR template
    gained a roadmap-item line.

v0.1.7

Choose a tag to compare

@github-actions github-actions released this 24 Oct 09:46
c9248b9

Added

  • publish_on_close hardening: CHANGE_NOTIFY event mapping. When
    publish_on_close is enabled on a share, the CHANGE_NOTIFY watcher now
    maps inotify events on hidden temp files (.rssmb-tmp.*) to events on
    the client-visible final name, so no temp name ever surfaces to SMB
    clients:
    • temp ADDED → FILE_ACTION_ADDED <final_name>
    • temp MODIFIED → FILE_ACTION_MODIFIED <final_name>
    • temp REMOVED → suppress (abort, delete-on-close, or publish rename)
      Also confirmed via unit tests that (a) a durable handle's close
      publishes its pending temp, (b) the startup orphan sweep runs on
      share mount (not only from the periodic sweeper), and (c) fresh temps
      survive the startup sweep.
  • publish_on_close integration tests and CI. Added a
    publish-on-close job to the interop CI workflow that runs the
    impacket two-client scenario (A creates+writes, B lists+opens during
    pending, host ls -A shows only temp, B closes → publish) and a
    rclone --inplace test with inotifywait (asserting only MOVED_TO on
    the final name). Transcripts are uploaded as CI artifacts. The nightly
    smbtorture baseline now also runs with publish_on_close ON
    (tests/live/rssmb-live-poc.toml) and compares against
    results-poc.md.

Fixed

  • publish_on_close: abort orphaning data on attach rollback. When
    an attached handle (a second CREATE on a pending name) rolled back to
    zero open_count via abort(), the temp file — carrying another
    handle's written data — was orphaned (entry removed, temp left on
    disk with no pending entry to publish it). abort() now returns
    (temp, delete_on_close) — the same shape as close() — and the
    rollback_on_err macro publishes the temp (rename → final) instead
    of orphaning it. Only a freshly-created empty temp (created_here)
    or a delete_on_close temp is unlinked.
  • publish_on_close: rename + take_destination not atomic. The
    three-step rename → take_destination → rename dance held the
    table-level write lock three separate times, leaving a window where
    both source and destination were absent from the map. Replaced with
    rename_with_replace() — one method, one write lock, one atomic
    table-level operation that does the move and collision handling
    (including discarding a displaced destination) in a single lock hold.
  • publish_on_close: close race — rename under lock. The close()
    path removed the pending entry and returned the temp path before the
    caller performed the filesystem rename, leaving a window where a
    racing resolve_or_acquire could miss the entry and create a phantom
    re-pend. Replaced with close_publish() which calls the rename
    closure under the table's write lock — the entry is removed
    atomically with the filesystem rename, so no racing acquire can
    observe the gap.
  • publish_on_close: attach race — no phantom entry. The
    pending-file probe in LocalFs::create used resolve_or_acquire
    which minted a Created entry and immediately aborted it, leaving a
    window where a racing close or resolve_or_acquire could see the
    phantom entry. Replaced with attach() which finds an existing
    pending entry and increments its count, or returns None — no mint,
    no abort, no phantom.
  • Credit window: credit_charge=0 consumed a MessageId. Per MS-SMB2
    §3.3.1.2, credit_charge=0 MUST be treated as 1 for the credit
    balance
    (available credits), but the MessageId sequence window
    must use the actual credit_charge value (0 = no MessageIds
    consumed). The server incorrectly treated credit_charge=0 as 1 for
    both, consuming MessageId M for a zero-charge WRITE and then
    rejecting a CLOSE with the same MessageId M as "already used".
    Fixed by separating the balance charge (max(charge, 1)) from the
    window charge (actual charge) in CreditManager::check_and_deduct.
    Also added a balance-availability check so a zero-charge request with
    no available credits still fails (instead of silently saturating to
    0). This fixes the impacket smb.close()/logoff() connection
    terminations that forced the publish-on-close integration test to
    work around them by dropping the TCP socket.

Changed

  • multichannel default flipped to false. SMB 3.x multichannel is
    now off by default until binding hardening lands; set multichannel = true
    in the server config to re-enable. The server still honors the capability
    when explicitly enabled.

Security

  • NTLMv2 timestamp freshness window (ntlm_max_clock_skew_secs,
    default 600).
    When ntlm_require_timestamp is true, the MsvAvTimestamp
    in the AUTHENTICATE message is now checked for freshness (±10 min by
    default) against the server clock, not just presence. A stale timestamp
    is rejected as a replay attempt. Set 0 for the old presence-only
    behavior.

v0.1.6

Choose a tag to compare

@github-actions github-actions released this 23 Oct 17:52
cf4e1ca

Added

  • Byte-range locks: smb2.lock 22/22 (was 3/22; smb2.durable-v2-open
    14/15, smb2.lease 30/9, smbtorture 4.19.5).
    Campaign C brings the
    lock suite to full golden parity with Samba 4.19.5 (22 pass / 3
    cluster-only skips):
    • Blocking locks (MS-SMB2 3.3.5.14). A lock without
      FAIL_IMMEDIATELY that conflicts with an existing range now pends
      as STATUS_PENDING and is granted when the conflicting range is
      released, or LOCK_NOT_GRANTED after a 35 s timeout. CANCEL
      completes the pended lock with STATUS_CANCELLED; CLOSE,
      TREE_DISCONNECT, and LOGOFF complete it with
      RANGE_NOT_LOCKED. Completion ownership is strict — only the path
      that removes the pending-async entry sends the one out-of-band
      response, preventing INVALID_NETWORK_RESPONSE from a double-send.
    • LockSequence replay detection (MS-SMB2 3.3.5.14). Per-open
      64-bucket array (sequence >> 4 = bucket, & 0xF = value);
      replayed successful sequences are silently suppressed. Enabled for
      all opens (SMB2_CAP_MULTI_CHANNEL), matching smbd — this is also
      what makes replay_broken_windows SKIP with golden parity.
    • Same-open conflict semantics. Re-acquiring an exclusive lock
      over an existing own lock conflicts (LOCK_NOT_GRANTED, no
      auto-unlock); shared locks stack freely over own locks; the open's
      own shared locks block its own writes (the point of a shared lock).
    • smbd-exact flag validation. First-element switch determines
      unlock vs lock mode; SHARED|UNLOCK etc → INVALID_PARAMETER;
      unlock batching (applied unlocks persist, first miss →
      RANGE_NOT_LOCKED); zero-length locks are valid stacked entries;
      offset+length overflow → INVALID_LOCK_RANGE (but max/1 is
      valid — only true overflow errors).

Fixed

  • Four wrong NTSTATUS wire values (K1). Full audit of all 32 enum
    variants + 15 raw Other(0x…) literals against Wine's ntstatus.h
    (2717 defines, MS-ERREF §2.3.1 mirror): LOCK_NOT_GRANTED was
    0xC000005A (STATUS_INVALID_OWNER) → 0xC0000055; FILE_LOCK_CONFLICT
    was 0xC0000070 (STATUS_INVALID_WORKSTATION) → 0xC0000054;
    DuplicateObjectId was 0xC0000042 (STATUS_INVALID_PORT_HANDLE) →
    0xC000022A; PIPE_EMPTY was 0xC0000050 (STATUS_EA_TOO_LARGE) →
    0xC00000D9. New named variants replace the raw literals so this
    class of bug cannot silently recur.
  • 0-byte READ killed the connection. The ReadResponse encoder
    asserted !buffer.is_empty() and the wire encoder returned empty,
    terminating the connection — a 0-byte READ (Length=0) is legal
    (torture zerobyteread) and now returns OK + 0 bytes before the
    lock-conflict check.
  • CHANGE_NOTIFY no longer hangs or dies with the session (S1,
    session lifecycle). Six server fixes, all verified against smbtorture
    4.19.5 (smb2.notify no longer stalls the nightly; close, logoff,
    session-reconnect, tdis1 now pass): CLOSE of a handle cancels
    only that handle's watch with STATUS_NOTIFY_CLEANUP; pre-interim
    CANCEL (mid=0) cancels the newest pending op; TDIS/LOGOFF complete
    pending watches; durable reconnect re-binds watches; deleting the
    watched directory completes the watch; CLOSE-cleanup no longer
    self-deadlocks.
  • TREE_DISCONNECT of an already-disconnected tree returned
    ACCESS_DENIED
    (was NETWORK_NAME_DELETED, MS-SMB2 3.3.5.9;
    torture connect.c:234). smb2.connect now passes fully.
  • Second LOGOFF returned OK (was USER_SESSION_DELETED,
    MS-SMB2 3.3.5.6; torture connect.c:248).

v0.1.5

Choose a tag to compare

@github-actions github-actions released this 21 Oct 13:42
4d1362b

Added

  • Per-share atomic publish-on-close (publish_on_close, default false).
    New files on shares with the option enabled are written under a hidden
    temp name (.rssmb-tmp.<final-name>.<8 hex>, same directory) and atomically
    renamed to the final name when the last SMB handle closes. SMB clients
    observe the final name throughout (CREATE by name, QUERY_DIRECTORY,
    QUERY_INFO, rename, delete, CHANGE_NOTIFY, case-insensitive lookup all go
    through a single VFS resolution hook); only the backing filesystem sees the
    temp name. Fixes host-side consumers (watchers, ingest pipelines, backups)
    reading a pre-sized file with zero data while a client is still writing.
    Covers CREATE / OPEN_IF-when-creating / OVERWRITE_IF / SUPERSEDE on regular
    files; plain OPEN of an existing file is untouched (deliberate). Durable
    handles carry the pending entry (reconnect resumes it; durable expiry
    publishes as a close). Crash orphans (.rssmb-tmp.*, no map entry, older
    than 1 h) are removed at share startup and by a 10-minute sweep in the
    server sweeper.

v0.1.4

Choose a tag to compare

@github-actions github-actions released this 21 Oct 07:32
v0.1.4
f493ab7

Added

  • Leases: smbtorture smb2.lease 28/36 (was 3/36 at v0.1.3;
    Samba 4.19.5 reference: 34/36). Deferred CREATEs now pend as
    STATUS_PENDING and complete on lease-break ACK or the 35 s timeout
    (L1), and lease/oplock grant rules match Samba's — upgrades grant only
    the requested level per the current-open context, overwrite/truncate
    breaks to NONE, rename and sharing violations break only handle caching,
    stepped re-breaks keep read caching while write/handle caching is held
    (L2). Remaining 8 failures tracked in tests/conformance/results.md
    (L1.1 rename_wait intermittent, L2.1 ACK_REQUIRED, L2.2 v2 epoch).
  • Durable handles v2/v1 conformance — smb2.durable-v2-open 14/15
    (was 0/15; smbtorture 4.19.5). DHnQ/DHnC v1 contexts are parsed alongside
    DH2Q/DH2C; reconnects look up the durable open by the persistent file id
    (Samba's model), verify the CreateGuid for v2, and honor orphan gating — a
    durable only reconnects after its owning connection dropped. Durable
    opens survive metadata changes, multiple reconnect cycles, and
    PreviousSessionId session reconnects (old session torn down; its traffic
    then fails with USER_SESSION_DELETED, mirroring Samba). Lease-backed
    durables validate the lease key and are scoped to the client GUID.
  • Background durable sweeper task: expired orphaned durables are evicted
    (VFS handles closed) without waiting for client traffic to touch the
    table; a live connection's durable never expires — the timeout counts
    from the disconnect.

Fixed

  • Cargo builds with -D warnings failed (the setting CI uses): 21
    clippy lints (needless clones, ignored Results, unit-assert
    comparisons) and one tokio set_times shadowing in the vfs. No
    behavior changes intended; the rustls-webpki 0.103.16 bump is for
    RUSTSEC-2026-0382.
  • CREATE responses reported wrong create_action and file attributes.
    Every open answered create_action=Opened; created files now correctly
    report Created, superseded files Superseded, overwritten files
    Overwritten (the VFS create now reports the action). New files reported
    FILE_ATTRIBUTE_ARCHIVE|NORMAL; NORMAL is now only set when no other
    attribute applies (MS-FSCC). Side effect: smb2.create 5/12 → 6/11,
    smb2.lease 3/36 → 8/31 (assertions on those fields), smb2.session
    44/22 → 46/20.
  • Concurrent v1 durable opens corrupted each other. All DHnQ durables
    shared one zero-GUID table key, so a second v1 open evicted the first
    and closed its file handle mid-session (Linux cifs clients request a v1
    durable on every SMB 2.1 open). v1 durables are keyed by their SMB
    persistent file id, matching Samba.
  • A durable open's VFS handle could be closed while still in use when
    the durable timeout elapsed on a live connection; the timeout now starts
    at disconnect (Samba: disconnect_time), not at open.
  • Strict CREATE parsing rejected legitimate durable-reconnect requests.
    Unknown OplockLevel / ImpersonationLevel / CreateDisposition wire values
    failed the whole request decode; these fields are advisory and ignored
    on reconnects (MS-SMB2 3.3.5.9.12), so they now parse leniently.

v0.1.3

Choose a tag to compare

@github-actions github-actions released this 18 Oct 18:43
237d8c0

Changed

  • READ throughput: requests are dispatched concurrently. Independent
    READ and ECHO requests now run in per-request tasks (bounded by the credit
    window), responses are encrypted in place, and full-size reads use
    positional pread on pooled buffers — no per-handle lock across the disk
    read and no per-read 1 MiB zeroed allocation. Measured (Linux CIFS 3.1.1,
    sealed, 1 MiB, direct, iodepth 16): 391 → ~550 MiB/s; iodepth 4:
    333 → ~889 MiB/s; unsealed iodepth 16: ~775 → ~1533 MiB/s. Compound
    requests and related-operation chains stay strictly sequential; response
    ordering is unchanged (out-of-order only for independent requests, which
    MS-SMB2 permits). The read-path profile and flamegraphs live in
    tests/perf/.

Fixed

  • smbtorture nightly job failed every suite with
    NT_STATUS_OBJECT_NAME_NOT_FOUND.
    The harness passed an empty host into
    the share UNC (environment variables were never exported to the job), and
    --option=torture:port is ignored by smbtorture 4.19.5 — the suite always
    dialed port 445. The harness now validates RSSMB_HOST/PORT/SHARE, uses the
    real -p/--smb-ports flag, and records per-subtest results; a baseline
    against 0.1.2 is committed at tests/conformance/results.md
    (115 passing / 190 failing subtests, with names).

Documentation

  • Features and roadmap now match the smbtorture baseline; durable handles v2
    and leases are marked partial with links to the conformance results, and
    the README gains a "Conformance status" section (115/305, four largest
    gaps listed).

v0.1.2

Choose a tag to compare

@github-actions github-actions released this 18 Oct 12:18
b5a41d9

Fixed

  • Full-size WRITE requests dropped the connection. The server advertised MaxWriteSize = 1 MiB but rejected any frame larger than max_frame_size (1 MiB), so a compliant client's 1 MiB WRITE (payload + SMB2/WRITE/transform headers) was treated as oversized and the connection closed. The wire limit now allows fixed header headroom above max_frame_size, and the advertised I/O sizes are derived from max_frame_size.
  • QUERY_DIRECTORY rejected FileDirectoryInformation and FileNamesInformation. The protocol enum for FileInformationClass used wrong MS-FSCC §2.4 codes (FileDirectoryInformation = 2 instead of 1, FileFullDirectoryInformation = 14 instead of 2) and had no FileNamesInformation (12). Requests with class 1 or 12 failed to decode and were answered with STATUS_INVALID_PARAMETER, which broke directory listing for go-smb2 / rclone. The response encoders were already keyed on the correct codes and are unchanged.
  • QUERY_DIRECTORY with an unsupported FileInformationClass returned STATUS_INVALID_PARAMETER. MS-SMB2 3.3.5.18 requires STATUS_INVALID_INFO_CLASS. The class byte is now decoded raw, so any non-directory class (e.g. 14 = FilePositionInformation, or undefined values) reaches the handler and gets STATUS_INVALID_INFO_CLASS.
  • NetrShareEnum / NetrShareGetInfo sent a NULL remark pointer for shares without a comment. Legal NDR, but go-smb2 (as bundled in rclone ≤ 1.75) reads a remark for every entry and failed share listing with "broken net share enum response format". Empty comments are now sent as the empty string, as Samba and Windows do. Referent IDs are now distinct (0x00020000, +4) instead of all 1.
  • NetrShareEnum wrote deferred strings in the wrong order, scrambling share names and comments. All netnames were emitted before all remarks, but NDR (C706 §14.3.12.3) requires each element's pointees in element order (name[0], remark[0], name[1], …). As soon as any share had a comment, clients paired names and comments wrongly (smbclient -L showed private with comment public). The reply is now byte-identical to Samba's for the same share set.
  • NetrShareGetInfo reply had an extra leading Level field. The [out] LPSHARE_INFO InfoStruct union is marshalled as discriminant + arm (MS-SRVS 3.1.4.10); rssmb wrote Level and then the discriminant, shifting every following field by 4 bytes. Strict NDR clients (e.g. impacket hNetrShareGetInfo) failed to parse the reply. It is now byte-identical to Samba's. The request parser also expected a referent ID before NetName, which is a top-level [ref] pointer with no wire representation (C706 §14.3.11.1), so real clients' requests were misread and answered with ERROR_INVALID_LEVEL.
  • SPNEGO first-leg negTokenResp omitted negState and supportedMech. RFC 4178 §4.2.2 requires both on the acceptor's first response. Strict clients (go-smb2 / rclone, Windows) rejected session setup with "server selected an unsupported mechanism"; smbclient and Linux CIFS tolerated the omission. Added an rclone (go-smb2) interop job to Interop CI that runs on every push and pull request to main, so a strict SPNEGO client now gates PRs.

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 17 Oct 23:21
v0.1.1
001f203

Fixed

  • WRITE acknowledged before data reached the file. write_at returned as
    soon as tokio had buffered the data, while the actual write(2) still ran in
    the background. A follow-up QUERY_INFO, CLOSE or re-open could observe the
    old file size or contents, and write errors (e.g. disk full) were silently
    lost after the client had already been told the write succeeded. Writes now
    complete (without fsync) before the WRITE response is sent.