Repository navigation
Releases
Releases · treeol/rssmb
v0.1.10
Changed
- R-30 closed as cannot-reproduce; repro pinned as the
r30_reopenlive
scenario (#49). The reported symptom (READ answering an empty body with
STATUS_OKafter CREATE/WRITE/CLOSE + re-open) does not reproduce on the
claimed-failing commitd791d4dnor onmain(dialects 2.1/3.0/3.1.1,
impacket 0.13.1); the exact sequence is committed astests/livescenario
r30_reopenso it can't silently regress. Past-EOF READ semantics
tracked under R-11. - EOF READ semantics pinned as the
r11_eoflive 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.shreproduces 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 (fioverify=crc32c2 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 totests/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 zerofailure:lines, and the old tally
(total - fail) then counted every started subtest as passed. Both
run-smbtorture.shandrun-smbtorture-poc.shnow 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 onsmb2.compound.related1when the
server negotiates no cipher (smb2cli_session_set_id_and_flags
NULL deref) — reproduced identically against unmodified smbd
4.19.5 withsmb encrypt = off; run the test server with
encryption = "desired".
v0.1.9
Fixed
- SMB2 credit grants now match smbd 4.19.5 (
smb2.credits0/3 → 3/3,
ROADMAP R-05). Three defects, found by capturing Samba 4.19.5's
per-response grants withsmb2_credits:5debug logging and diffing
against rssmb's (extracted fromsource3/smbd/smb2_server.c
smb2_set_operation_credit):max_creditsdefault 1024 → 8192 — smbd'ssmb2 max credits
default; the torture subtests assert ≥ 8192 grants after session
setup. Alltests/live/*.tomlconfigs follow.- 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'sSMB2_OP_NEGPROT/failed-SMB2_OP_SESSSETUPcases).
The oldgrant()capped by outstanding balance only, so the client
balance could never converge to the window while requests were
in flight. - Zero-charge requests no longer reuse their MessageId. smbd
chargesMAX(CreditCharge, 1)to the MessageId window too
(smb2_validate_message_id); the old zero-charge workaround
(window consumed 0, MessageId reusable) pinnedlowbehind
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
lowlike 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
(incheck_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_credits8192; rssmb now byte-identical. All 19 live interop
scenarios (impacket/smbclient-style, incl.bigwrite,rename,
r30_reopen,r11_eof) still pass;smb2.readdelta 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:
deadidentitiesboot Arc removed (all auth paths use the
live-reloadidentities_snapshot()), deadadd_sessionremoved,
and the canonical session-table order (registry → sessions →
retired_sessions, never overlapping) documented in
docs/lock-audit.mdalong with the intentional std/tokio flavor
split for signing/encryption vs trees/opens. The audit also records
known follow-ups found during it: the globalsessionsVec 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_locknow passed explicitly (-D) in
both workflows, alongside-Dclippy::all— the lint was already
covered byclippy::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'ssspi 0.23requires 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-versionin 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 newmsrvCI job runs
cargo check --workspace --lockedon the 1.93 toolchain so the
declared floor can't silently rot again.
v0.1.8
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):aes0.8.4 → 0.9.3 with companion bumpsaes-gcm0.10 → 0.11 and
cmac0.7 → 0.8. AES-128-GCM encrypt/decrypt and CMAC signing
adapted to the newaeadAPI surface (encrypt/decrypt_inout_detached,
Arrayconversions viaTryFrom/Into); all 35 crypto unit tests
pass unchanged, wire behavior byte-identical.rand0.8.8 → 0.10.3 (rng(),Rng,random_range,SysRng+
UnwrapErrAPI adaptation — affects negotiate-context nonce
generation only).cap-std/cap-primitives3.4.6 → 4.0.3 (family kept on one
version to avoid dualFollowSymlinkstypes in the graph).sspi0.21.3 → 0.23.0,thiserror1.0.69 → 2.0.21,
libc0.2.190 → 0.2.191,unicode-normalization0.1.25 → 0.1.26,
md40.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.mdnow point at the roadmap; PR template
gained a roadmap-item line.
v0.1.7
Added
publish_on_closehardening: 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.
- temp ADDED →
publish_on_closeintegration tests and CI. Added a
publish-on-closejob to the interop CI workflow that runs the
impacket two-client scenario (A creates+writes, B lists+opens during
pending, hostls -Ashows only temp, B closes → publish) and a
rclone--inplacetest with inotifywait (asserting onlyMOVED_TOon
the final name). Transcripts are uploaded as CI artifacts. The nightly
smbtorture baseline now also runs withpublish_on_closeON
(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 viaabort(), 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 asclose()— and the
rollback_on_errmacro publishes the temp (rename → final) instead
of orphaning it. Only a freshly-created empty temp (created_here)
or adelete_on_closetemp is unlinked.publish_on_close: rename + take_destination not atomic. The
three-steprename → take_destination → renamedance 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. Theclose()
path removed the pending entry and returned the temp path before the
caller performed the filesystem rename, leaving a window where a
racingresolve_or_acquirecould miss the entry and create a phantom
re-pend. Replaced withclose_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 inLocalFs::createusedresolve_or_acquire
which minted aCreatedentry and immediately aborted it, leaving a
window where a racingcloseorresolve_or_acquirecould see the
phantom entry. Replaced withattach()which finds an existing
pending entry and increments its count, or returnsNone— no mint,
no abort, no phantom.- Credit window:
credit_charge=0consumed a MessageId. Per MS-SMB2
§3.3.1.2,credit_charge=0MUST be treated as 1 for the credit
balance (available credits), but the MessageId sequence window
must use the actualcredit_chargevalue (0 = no MessageIds
consumed). The server incorrectly treatedcredit_charge=0as 1 for
both, consuming MessageIdMfor a zero-charge WRITE and then
rejecting a CLOSE with the same MessageIdMas "already used".
Fixed by separating the balance charge (max(charge, 1)) from the
window charge (actualcharge) inCreditManager::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 impacketsmb.close()/logoff()connection
terminations that forced the publish-on-close integration test to
work around them by dropping the TCP socket.
Changed
multichanneldefault flipped tofalse. SMB 3.x multichannel is
now off by default until binding hardening lands; setmultichannel = 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). Whenntlm_require_timestampis 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. Set0for the old presence-only
behavior.
v0.1.6
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_IMMEDIATELYthat conflicts with an existing range now pends
asSTATUS_PENDINGand is granted when the conflicting range is
released, orLOCK_NOT_GRANTEDafter a 35 s timeout.CANCEL
completes the pended lock withSTATUS_CANCELLED;CLOSE,
TREE_DISCONNECT, andLOGOFFcomplete 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, preventingINVALID_NETWORK_RESPONSEfrom 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 makesreplay_broken_windowsSKIP 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|UNLOCKetc →INVALID_PARAMETER;
unlock batching (applied unlocks persist, first miss →
RANGE_NOT_LOCKED); zero-length locks are valid stacked entries;
offset+lengthoverflow →INVALID_LOCK_RANGE(butmax/1is
valid — only true overflow errors).
- Blocking locks (MS-SMB2 3.3.5.14). A lock without
Fixed
- Four wrong NTSTATUS wire values (K1). Full audit of all 32 enum
variants + 15 rawOther(0x…)literals against Wine'sntstatus.h
(2717 defines, MS-ERREF §2.3.1 mirror):LOCK_NOT_GRANTEDwas
0xC000005A(STATUS_INVALID_OWNER) →0xC0000055;FILE_LOCK_CONFLICT
was0xC0000070(STATUS_INVALID_WORKSTATION) →0xC0000054;
DuplicateObjectIdwas0xC0000042(STATUS_INVALID_PORT_HANDLE) →
0xC000022A;PIPE_EMPTYwas0xC0000050(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
ReadResponseencoder
asserted!buffer.is_empty()and the wire encoder returned empty,
terminating the connection — a 0-byte READ (Length=0) is legal
(torturezerobyteread) and now returnsOK+ 0 bytes before the
lock-conflict check. CHANGE_NOTIFYno longer hangs or dies with the session (S1,
session lifecycle). Six server fixes, all verified against smbtorture
4.19.5 (smb2.notifyno longer stalls the nightly;close,logoff,
session-reconnect,tdis1now pass): CLOSE of a handle cancels
only that handle's watch withSTATUS_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(wasNETWORK_NAME_DELETED, MS-SMB2 3.3.5.9;
tortureconnect.c:234).smb2.connectnow passes fully. - Second LOGOFF returned
OK(wasUSER_SESSION_DELETED,
MS-SMB2 3.3.5.6; tortureconnect.c:248).
v0.1.5
Added
- Per-share atomic publish-on-close (
publish_on_close, defaultfalse).
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
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 intests/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 warningsfailed (the setting CI uses): 21
clippy lints (needless clones, ignoredResults, unit-assert
comparisons) and one tokioset_timesshadowing 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 answeredcreate_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
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
positionalpreadon 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:portis 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-portsflag, and records per-subtest results; a baseline
against 0.1.2 is committed attests/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
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 abovemax_frame_size, and the advertised I/O sizes are derived frommax_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 noFileNamesInformation(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-Lshowedprivatewith commentpublic). The reply is now byte-identical to Samba's for the same share set. - NetrShareGetInfo reply had an extra leading
Levelfield. The[out] LPSHARE_INFO InfoStructunion is marshalled as discriminant + arm (MS-SRVS 3.1.4.10); rssmb wroteLeveland then the discriminant, shifting every following field by 4 bytes. Strict NDR clients (e.g. impackethNetrShareGetInfo) failed to parse the reply. It is now byte-identical to Samba's. The request parser also expected a referent ID beforeNetName, which is a top-level[ref]pointer with no wire representation (C706 §14.3.11.1), so real clients' requests were misread and answered withERROR_INVALID_LEVEL. - SPNEGO first-leg negTokenResp omitted
negStateandsupportedMech. 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 tomain, so a strict SPNEGO client now gates PRs.
v0.1.1
Fixed
- WRITE acknowledged before data reached the file.
write_atreturned as
soon as tokio had buffered the data, while the actualwrite(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.