fix(smb): bound file reads before buffering - #7624
Conversation
Walkthrough
ChangesSMB bounded read flow
HTTP integration test serialization
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant SessionReadFile
participant Backend
participant MountedShare
Caller->>SessionReadFile: ReadFile(path, maxBytes)
alt Injected backend
SessionReadFile->>Backend: Open(path)
Backend-->>SessionReadFile: File handle
else Native client
SessionReadFile->>MountedShare: Open(path)
MountedShare-->>SessionReadFile: File handle
end
SessionReadFile->>SessionReadFile: readBoundedFile(maxBytes)
SessionReadFile-->>Caller: Content or read error
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
pkg/js/libs/smbsession/session.go (1)
214-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared validation logic from
readFileandreadClientFile.
readFile(Lines 214-227) andreadClientFile(Lines 244-256) repeat the same four steps:RequireShareName,maxBytesdefault,NormalizeSharePath, and the empty-path check. Extract this into one helper that both functions call before they diverge on the connection mechanism. This reduces the risk that a future validation change is applied to only one path.♻️ Proposed refactor
+func prepareReadFile(share, filePath string, maxBytes int64) (string, int64, error) { + if err := RequireShareName(share); err != nil { + return "", 0, err + } + if maxBytes <= 0 { + maxBytes = DefaultMaxReadBytes + } + normalized, err := NormalizeSharePath(filePath) + if err != nil { + return "", 0, err + } + if normalized == "." { + return "", 0, fmt.Errorf("file path cannot be empty") + } + return normalized, maxBytes, nil +} + func readFile(ops shareBackend, share, filePath string, maxBytes int64) (string, error) { - if err := RequireShareName(share); err != nil { - return "", err - } - if maxBytes <= 0 { - maxBytes = DefaultMaxReadBytes - } - normalized, err := NormalizeSharePath(filePath) + normalized, maxBytes, err := prepareReadFile(share, filePath, maxBytes) if err != nil { return "", err } - if normalized == "." { - return "", fmt.Errorf("file path cannot be empty") - } if err := ops.UseShare(share); err != nil { return "", fmt.Errorf("mount share %q: %w", share, err) } opener, ok := ops.(shareOpener) if !ok { return "", fmt.Errorf("SMB backend does not support bounded file reads") } f, err := opener.Open(normalized) if err != nil { return "", err } defer func() { _ = f.Close() }() return readBoundedFile(f, normalized, maxBytes) } func readClientFile(client *gpsmb.Client, share, filePath string, maxBytes int64) (string, error) { - if err := RequireShareName(share); err != nil { - return "", err - } - if maxBytes <= 0 { - maxBytes = DefaultMaxReadBytes - } - normalized, err := NormalizeSharePath(filePath) + normalized, maxBytes, err := prepareReadFile(share, filePath, maxBytes) if err != nil { return "", err } - if normalized == "." { - return "", fmt.Errorf("file path cannot be empty") - } mountedShare, err := client.Session.Mount(share)Also applies to: 243-270
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/js/libs/smbsession/session.go` around lines 214 - 241, Extract the shared RequireShareName, maxBytes defaulting, NormalizeSharePath, and empty-path validation from readFile and readClientFile into a helper returning the validated share, normalized path, and effective limit. Update both functions to call this helper before their separate backend/connection logic, preserving existing validation errors and defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/js/libs/smbsession/session.go`:
- Around line 243-270: Add coverage for the native-client path by testing
readClientFile through a gpsmb.Client returned by FromClient, using
client.Session.Mount to open a fixture file and exercising readBoundedFile with
limits below and above the file size. Preserve existing validation and error
behavior while asserting bounded content and the relevant read errors.
---
Nitpick comments:
In `@pkg/js/libs/smbsession/session.go`:
- Around line 214-241: Extract the shared RequireShareName, maxBytes defaulting,
NormalizeSharePath, and empty-path validation from readFile and readClientFile
into a helper returning the validated share, normalized path, and effective
limit. Update both functions to call this helper before their separate
backend/connection logic, preserving existing validation errors and defaults.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d0ab2f04-c18d-4d56-a40e-304dbdafab5a
📒 Files selected for processing (3)
internal/tests/integration/http_test.gopkg/js/libs/smbsession/session.gopkg/js/libs/smbsession/session_test.go
| func readClientFile(client *gpsmb.Client, share, filePath string, maxBytes int64) (string, error) { | ||
| if err := RequireShareName(share); err != nil { | ||
| return "", err | ||
| } | ||
| body, err := ops.Cat(normalized) | ||
| if maxBytes <= 0 { | ||
| maxBytes = DefaultMaxReadBytes | ||
| } | ||
| normalized, err := NormalizeSharePath(filePath) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| if normalized == "." { | ||
| return "", fmt.Errorf("file path cannot be empty") | ||
| } | ||
|
|
||
| mountedShare, err := client.Session.Mount(share) | ||
| if err != nil { | ||
| return "", fmt.Errorf("mount share %q: %w", share, err) | ||
| } | ||
| defer func() { _ = mountedShare.Umount() }() | ||
|
|
||
| f, err := mountedShare.Open(normalized) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| defer func() { _ = f.Close() }() | ||
| return readBoundedFile(f, normalized, maxBytes) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Search for any test exercising readClientFile or Session{client: ...} in smbsession.
rg -n 'readClientFile|client:\s*&?gpsmb' --type=go -C3Repository: projectdiscovery/nuclei
Length of output: 161
🏁 Script executed:
#!/bin/bash
set -u
echo "Find smbsession files:"
fd -a 'session(_test)?\.go|.*\.go$' pkg/js/libs/smbsession 2>/dev/null | sed 's#^\./##' || find pkg/js/libs/smbsession -type f -name '*.go'
echo
echo "Session-related tests/files outlines:"
for f in $(fd 'session(_test)?\.go$' pkg/js/libs/smbsession 2>/dev/null); do
echo "--- $f ($(wc -l < "$f") lines) ---"
ast-grep outline "$f" 2>/dev/null || true
done
echo
echo "Search read paths in smbsession:"
rg -n 'func read|readClientFile|readFile|readBoundedFile|func.*Read|ReadFile|Read|Mount\(|Open\(' pkg/js/libs/smbsession --type=go -C 2 || true
echo
echo "Search tests/names for bounded/native/client backend in repository:"
rg -n 'TestRead|readClientFile|readFile|readBoundedFile|Bound|Backend|Native|gpsmb\.Client|Session\{[^}]*client|{[^}]*client' --type=go -C 2 || true
echo
echo "Git status/diff stat:"
git diff --stat || true
git diff --name-only || trueRepository: projectdiscovery/nuclei
Length of output: 15875
🏁 Script executed:
#!/bin/bash
set -u
echo "Repository-wide call sites for FromClient/Rfile/native paths:"
rg -n 'FromClient|FromFile|ReadFile\(|readClientFile|readBoundedFile|Native\(\)|LimitReader|DefaultMaxReadBytes' --type=go -C 2 || true
echo
echo "All Go test mentions of read bounded/client/native/fake:"
rg -n 'ReadFile|readFile|readClientFile|readBoundedFile|FromClient|Native|Limit|Oversize|backend' --type=test -C 2 || true
echo
echo "Session tests only relevant lines:"
sed -n '1,120p' pkg/js/libs/smbsession/session_test.goRepository: projectdiscovery/nuclei
Length of output: 431
Add coverage for readClientFile bounded reads.
The existing session_test.go tests only create Session{backend: ...}, so they exercise readFile without covering the native-client path returned by FromClient. Add tests or integration coverage for readClientFile bounded reads through client.Session.Mount and readBoundedFile.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/js/libs/smbsession/session.go` around lines 243 - 270, Add coverage for
the native-client path by testing readClientFile through a gpsmb.Client returned
by FromClient, using client.Session.Mount to open a fixture file and exercising
readBoundedFile with limits below and above the file size. Preserve existing
validation and error behavior while asserting bounded content and the relevant
read errors.
Summary
Root cause
The previous implementation checked the size only after the SMB client had buffered the complete file.
Validation
ok github.com/projectdiscovery/nuclei/v3/pkg/js/libs/dcerpc (cached)
ok github.com/projectdiscovery/nuclei/v3/pkg/protocols/file (cached)
Summary by CodeRabbit