-
Description:
- The
Compress4X
andcompress4Xp
functions inhuff0/compress.go
split the input data into four segments and compress each segment independently. - For each segment (except the last one), the compressed length is written as a
uint16
(2 bytes) in little-endian format before the compressed data of that segment. - If the compressed size of any of the first three segments exceeds
math.MaxUint16
(65535 bytes), an integer overflow will occur when calculating and storing the length. - During decompression, the
Decompress4X
function reads these 2-byte length values to determine the boundaries of each compressed segment. Due to the integer overflow during compression, a small length value might be read, leading to incorrect segment boundaries. - This can cause
Decompress4X
to read beyond the intended segment boundary, potentially leading to out-of-bounds read during decompression or incorrect decompression of the data.
- The
-
Impact:
- Data corruption: Decompressed data may be incorrect due to misinterpretation of segment boundaries.
- Potential out-of-bounds read: During decompression, incorrect segment lengths might cause the decoder to read beyond the allocated buffer for a segment, potentially leading to crashes or information disclosure if sensitive data is located beyond the buffer. While Go is memory-safe, this could still lead to unexpected behavior and data corruption.
-
Vulnerability Rank: high
-
Currently Implemented Mitigations:
- In
Compress4X
andcompress4Xp
, there is a check:This check prevents the compression from proceeding if a single segment's compressed size exceedsif len(s.Out)-idx > math.MaxUint16 { // We cannot store the size in the jump table return nil, ErrIncompressible }
math.MaxUint16
. However, this check only returnsErrIncompressible
, effectively denying compression for such inputs, but does not mitigate the vulnerability if somehow a crafted input manages to bypass this check or if this check is removed in future versions, or if the size calculation itself is flawed leading to incorrect check results.
- In
-
Missing Mitigations:
- Proper error handling and propagation when compressed segment size exceeds the limit. Returning
ErrIncompressible
prevents compression, but ideally, a more specific error indicating segment size overflow should be used for better debugging and handling. - During decompression (
Decompress4X
and related functions not in the provided files, but assumed to exist), robust validation of the segment lengths read from the compressed data is crucial. The decompression logic should check if the read segment lengths are within reasonable bounds and handle potential overflows or corrupted length values gracefully, possibly returning an error instead of proceeding with potentially unsafe memory operations. - Consider using a larger data type than
uint16
to store segment lengths if segment sizes are expected to potentially exceed this limit in compressed form. However, this would change the format. A better approach might be to ensure segment sizes are limited during compression or implement more robust error handling during decompression when unexpected segment lengths are encountered.
- Proper error handling and propagation when compressed segment size exceeds the limit. Returning
-
Preconditions:
- Input data must be compressible by
huff0
algorithm. - Input data should be such that when compressed using
Compress4X
orCompress4Xp
, at least one of the first three segments compresses to a size greater thanmath.MaxUint16
bytes after the huff0 compression itself. This might require a relatively large input.
- Input data must be compressible by
-
Source Code Analysis: File:
/code/huff0/compress.go
var sixZeros [6]byte func (s *Scratch) compress4X(src []byte) ([]byte, error) { if len(src) < 12 { return nil, ErrIncompressible } segmentSize := (len(src) + 3) / 4 // Add placeholder for output length offsetIdx := len(s.Out) s.Out = append(s.Out, sixZeros[:]...) for i := 0; i < 4; i++ { toDo := src if len(toDo) > segmentSize { toDo = toDo[:segmentSize] } src = src[len(toDo):] idx := len(s.Out) s.Out = s.compress1xDo(s.Out, toDo) if len(s.Out)-idx > math.MaxUint16 { // Vulnerability Check // We cannot store the size in the jump table return nil, ErrIncompressible } // Write compressed length as little endian before block. if i < 3 { // Last length is not written. length := len(s.Out) - idx s.Out[i*2+offsetIdx] = byte(length) s.Out[i*2+offsetIdx+1] = byte(length >> 8) // Integer Overflow Point } } return s.Out, nil }
In the
compress4X
function, after compressing each of the first three segments usings.compress1xDo(s.Out, toDo)
, the code calculates the compressedlength
aslen(s.Out) - idx
. Thislength
is then cast tobyte
andbyte(length >> 8)
and stored ins.Out
. Iflength
is greater thanmath.MaxUint16
, the cast tobyte
andbyte(length >> 8)
will result in an integer overflow, storing incorrect length values. The checkif len(s.Out)-idx > math.MaxUint16
prevents the function from returning compressed data in such cases, but it's a check to prevent proceeding with compression, not a mitigation for the overflow itself if the compression was to proceed despite the size.The same logic applies to
compress4Xp
function:func (s *Scratch) compress4Xp(src []byte) ([]byte, error) { if len(src) < 12 { return nil, ErrIncompressible } // Add placeholder for output length s.Out = s.Out[:6] segmentSize := (len(src) + 3) / 4 var wg sync.WaitGroup wg.Add(4) for i := 0; i < 4; i++ { toDo := src if len(toDo) > segmentSize { toDo = toDo[:segmentSize] } src = src[len(toDo):] // Separate goroutine for each block. go func(i int) { s.tmpOut[i] = s.compress1xDo(s.tmpOut[i][:0], toDo) wg.Done() }(i) } wg.Wait() for i := 0; i < 4; i++ { o := s.tmpOut[i] if len(o) > math.MaxUint16 { // Vulnerability Check // We cannot store the size in the jump table return nil, ErrIncompressible } // Write compressed length as little endian before block. if i < 3 { // Last length is not written. s.Out[i*2] = byte(len(o)) s.Out[i*2+1] = byte(len(o) >> 8) // Integer Overflow Point } // Write output. s.Out = append(s.Out, o...) } return nil, ErrIncompressible // Should return s.Out, nil in case of success, but returns error because of copy-paste from compress4X }
-
Security Test Case:
- Prepare a large input data (e.g., several MB of compressible data). You might need to experiment to find data that compresses effectively with
huff0
and results in a compressed segment larger than 65535 bytes when usingCompress4X
orCompress4Xp
. Highly repetitive data or data with specific patterns might be more likely to trigger this. - Compress the input data using
Compress4X
orCompress4Xp
. - Examine the compressed output. Specifically, check the first 6 bytes of the output which are supposed to store the segment lengths for the first three segments. If the vulnerability is triggered, the lengths stored might be small values due to the integer overflow.
- Attempt to decompress the compressed data using
Decompress4X
. - Verify if the decompression process completes without errors, and if the decompressed output is the same as the original input. If data corruption occurs or if the decompression fails in an unexpected way (e.g., out-of-bounds read, panic, or incorrect output), it indicates a vulnerability.
- To reliably trigger, you might need to adjust the input data and potentially the
TableLog
parameter ofScratch
to influence the compression ratio and segment sizes. You might need to create a test input that is specifically designed to maximize the compression ratio of the first few segments to exceedmath.MaxUint16
when compressed.
Example Test Code (Conceptual - needs adaptation for a real test case):
package huff0_test import ( "bytes" "testing" "github.com/klauspost/compress/huff0" ) func TestCompress4XOverflow(t *testing.T) { // 1. Prepare large, compressible input (e.g., repeating pattern) inputData := bytes.Repeat([]byte("A"), 200*1024) // Example: 200KB of 'A's s := &huff0.Scratch{} // s.TableLog = 8 // You might need to adjust TableLog to influence compression // 2. Compress using Compress4X compressedData, _, err := huff0.Compress4X(inputData, s) if err != nil && err != huff0.ErrIncompressible { // Expect ErrIncompressible if check works as intended. t.Fatalf("Compression error: %v", err) } if err == huff0.ErrIncompressible { t.Skipf("Input deemed incompressible, which is expected mitigation. Trying to craft input to bypass.") // In a real test, you'd craft input to *try* to overflow, if possible. return } // 3. Examine compressed output (manual inspection recommended) // In automated test, check if first 6 bytes are suspiciously small (indicating overflow) if len(compressedData) > 6 { len1 := uint16(compressedData[0]) | uint16(compressedData[1])<<8 len2 := uint16(compressedData[2]) | uint16(compressedData[3])<<8 len3 := uint16(compressedData[4]) | uint16(compressedData[5])<<8 t.Logf("Segment lengths (uint16): %d, %d, %d", len1, len2, len3) // Check if any of these lengths are suspiciously small given input size. // For example, if input is large and lengths are like 0, 1, 2, it's suspicious. } // 4. Attempt decompression (if compression succeeded - though in theory, it should return ErrIncompressible) if compressedData != nil { // Only decompress if compression didn't return ErrIncompressible in a successful exploit scenario. decompressedData, err := huff0.Decompress4X(compressedData[6:], len(inputData)) // Assuming table is in first 6 bytes, data follows. Adjust if needed based on actual format. if err != nil { t.Errorf("Decompression error: %v", err) } else { // 5. Verify decompressed output if !bytes.Equal(inputData, decompressedData) { t.Errorf("Decompressed data does not match original input") } else { t.Log("Decompression successful, but vulnerability may still exist if lengths were overflowed during compression.") } } } else { t.Log("Compression returned ErrIncompressible as expected (mitigation triggered). Vulnerability prevented by design for this input.") } }
- Prepare a large input data (e.g., several MB of compressible data). You might need to experiment to find data that compresses effectively with
-
Description:
- The
s2sx
tool creates self-extracting archives that, when executed, unpack their contents. The unpacking logic is located in/code/s2/cmd/_s2sx/_unpack/main.go
within theuntar
function. - The
untar
function extracts files and directories from a tar archive embedded within the executable. - For each entry in the tar archive, the destination path is constructed using
filepath.Join(dst, header.Name)
, wheredst
is the extraction directory andheader.Name
is the name of the entry in the tar archive. - The
checkPath
function is called afterfilepath.Join
, which means ifheader.Name
contains path traversal sequences like../
,filepath.Join
will resolve them beforecheckPath
is invoked. - Consequently, a maliciously crafted tar archive with filenames starting with
../
can cause files to be extracted outside the intended destination directory (dst
), potentially overwriting system files or placing files in unintended locations.
- The
-
Impact:
- Arbitrary File Write: An attacker can craft a malicious self-extracting archive that, when executed by a user, can write files to arbitrary locations on the user's file system, limited by the permissions of the user running the executable.
- Potential for Escalation: If the user running the self-extracting archive has elevated privileges, this vulnerability could be used to escalate privileges or compromise the system.
- Data Corruption: Maliciously crafted archives could overwrite critical system files, leading to data corruption or system instability.
-
Vulnerability Rank: critical
-
Currently Implemented Mitigations:
- The
checkPath
function in/code/s2/cmd/_s2sx/_unpack/main.go
is intended to prevent path traversal. However, it is called afterfilepath.Join
, which resolves path traversal sequences before the check. -
This mitigation is ineffective against path traversal because
func checkPath(dst, filename string) error { dest := filepath.Join(dst, filename) //prevent path traversal attacks if !strings.HasPrefix(dest, dst) { return fmt.Errorf("illegal file path: %s", filename) } return nil }
filepath.Join
resolves..
beforecheckPath
is called.
- The
-
Missing Mitigations:
- Input validation of
header.Name
before using it infilepath.Join
. The code should check ifheader.Name
starts with../
or contains other path traversal sequences and reject such entries. - Secure path handling: Instead of relying on
filepath.Join
and post-hoc checks, use secure path manipulation functions that prevent path traversal by design, or implement a robust sanitization or validation of the path components before joining them.
- Input validation of
-
Preconditions:
- The victim must execute a maliciously crafted self-extracting archive created using
s2sx
. - The malicious archive must contain a tar archive with filenames designed to exploit the path traversal vulnerability (e.g., filenames starting with
../
).
- The victim must execute a maliciously crafted self-extracting archive created using
-
Source Code Analysis: File:
/code/s2/cmd/_s2sx/_unpack/main.go
func untar(dst string, r io.Reader) error { tr := tar.NewReader(r) for { header, err := tr.Next() // ... (error handling and header == nil checks) ... // the target location where the dir/file should be created if err := checkPath(dst, header.Name); err != nil { // Mitigation called AFTER filepath.Join return err } target := filepath.Join(dst, header.Name) // Path traversal happens here // ... (file type handling and extraction logic) ... } } func checkPath(dst, filename string) error { dest := filepath.Join(dst, filename) //prevent path traversal attacks if !strings.HasPrefix(dest, dst) { // Check is ineffective return fmt.Errorf("illegal file path: %s", filename) } return nil }
The vulnerability lies in the order of operations.
filepath.Join(dst, header.Name)
is called beforecheckPath(dst, header.Name)
. Ifheader.Name
is crafted to include path traversal sequences,filepath.Join
will resolve these sequences relative todst
. ThencheckPath
verifies if the resolved pathdest
still hasdst
as a prefix. However, becausefilepath.Join
has already performed the path traversal, the resolved pathdest
might no longer be within the intendeddst
directory, butcheckPath
will incorrectly validate it if the resolved path still happens to start with the originaldst
string (which is unlikely but theoretically possible in some edge cases, and even if not, the check is still bypassed if the resulting path is completely outsidedst
). -
Security Test Case:
- Create Malicious Tar Archive: Create a tar archive containing a file with a name like
../evil.txt
and some content. You can usetar
command-line tool for this:mkdir malicious_tar echo "This is evil content" > malicious_tar/evil.txt cd malicious_tar tar -cvf malicious.tar ../evil.txt cd ..
- Create Self-Extracting Archive: Use the
s2sx
tool to create a self-extracting archive from the malicious tar archive:This will creatego build -o s2sx ./code/s2/cmd/s2sx/main.go ./s2sx malicious_tar/malicious.tar
malicious_tar/malicious.tar.s2sx
(ormalicious.tar.s2sx.exe
on Windows). - Execute Self-Extracting Archive: Run the created self-extracting archive:
Replace
./malicious_tar/malicious.tar.s2sx <extraction_directory>
<extraction_directory>
with a directory where you expect the files to be extracted (e.g.,output_dir
). If you don't provide an argument, it will extract to the current working directory. - Verify Path Traversal: After execution, check if the file
evil.txt
has been created in a location outside the intended extraction directory (e.g., in the parent directory, if you extracted tooutput_dir
in the current directory, check ifevil.txt
is created in the current directory itself). Ifevil.txt
is found outside the intended directory, the path traversal vulnerability is confirmed.
Example Test Code (Conceptual - needs adaptation for automated testing):
package s2sx_test import ( "archive/tar" "bytes" "io" "os" "os/exec" "path/filepath" "testing" ) func TestS2SXPathTraversal(t *testing.T) { // 1. Create Malicious Tar Archive (in memory for test) var tarBuf bytes.Buffer tarWriter := tar.NewWriter(&tarBuf) evilContent := []byte("This is evil content") header := &tar.Header{ Name: "../evil.txt", // Path Traversal payload Mode: 0600, Size: int64(len(evilContent)), } if err := tarWriter.WriteHeader(header); err != nil { t.Fatal(err) } if _, err := tarWriter.Write(evilContent); err != nil { t.Fatal(err) } if err := tarWriter.Close(); err != nil { t.Fatal(err) } // 2. Create S2SX Archive (using s2sx command - needs binary built) s2sxBinary := "./s2sx" // Path to s2sx binary (build it before running test) if _, err := os.Stat(s2sxBinary); os.IsNotExist(err) { t.Skipf("s2sx binary not found at %s, skipping test", s2sxBinary) } tempDir, err := os.MkdirTemp("", "s2sx_test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tempDir) maliciousTarPath := filepath.Join(tempDir, "malicious.tar") if err := os.WriteFile(maliciousTarPath, tarBuf.Bytes(), 0644); err != nil { t.Fatal(err) } s2sxArchivePath := filepath.Join(tempDir, "malicious.tar.s2sx") cmd := exec.Command(s2sxBinary, maliciousTarPath) cmd.Dir = tempDir // Run command in temp dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("s2sx command failed: %v, output: %s", err, string(out)) } // 3. Execute S2SX Archive (unpack) extractionDir := filepath.Join(tempDir, "output_dir") if err := os.Mkdir(extractionDir, 0755); err != nil { t.Fatal(err) } cmdUnpack := exec.Command(s2sxArchivePath, extractionDir) cmdUnpack.Dir = tempDir // Run command in temp dir if out, err := cmdUnpack.CombinedOutput(); err != nil { t.Fatalf("s2sx unpack command failed: %v, output: %s", err, string(out)) } // 4. Verify Path Traversal - Check for evil.txt outside extractionDir traversedFilePath := filepath.Join(tempDir, "evil.txt") _, err = os.Stat(traversedFilePath) if !os.IsNotExist(err) { t.Errorf("Path traversal vulnerability exists! File found at: %s", traversedFilePath) } else { t.Logf("Path traversal prevented (or file not created): %s", traversedFilePath) } // Optionally, check if evil.txt is *not* in the extractionDir (as expected) evilInExtractionDir := filepath.Join(extractionDir, "evil.txt") _, err = os.Stat(evilInExtractionDir) if !os.IsNotExist(err) { t.Errorf("File should NOT be in extraction dir, but found at: %s", evilInExtractionDir) } }
- Create Malicious Tar Archive: Create a tar archive containing a file with a name like
-
Description:
- When using the default configuration, the gzhttp compression middleware produces deterministic compressed outputs.
- If an HTTP response intermingles fixed secret values (such as session tokens) with attacker–controlled input (for example, values coming from query parameters or form input), identical compression behavior may be observed.
- An external attacker can repeatedly request the same resource, varying the attacker–controlled parts, and analyze subtle changes (for example, via the Content–Length header) that correlate with the secret data.
-
Impact:
- An attacker may deduce sensitive information (such as CSRF or session tokens), which could result in session hijacking or unauthorized actions performed on behalf of a legitimate user.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- The gzhttp package includes an optional random–padding (jitter) mechanism designed to obfuscate these compression–size variations.
- However, this mechanism is disabled by default and must be explicitly enabled by developers.
-
Missing Mitigations:
- The default configuration does not enable the random–padding feature.
- There is no automatic detection mechanism for cases where sensitive fixed data is combined with attacker–controlled input.
-
Preconditions:
- The application is publicly exposed and uses the default gzhttp configuration.
- HTTP responses include both fixed secret values and attacker–influenceable content.
-
Source Code Analysis:
- A review of the gzhttp middleware reveals that with random jitter disabled, identical input data produces identical compression output.
- Although a helper function exists to inject random–padding, it is not invoked unless the middleware is explicitly configured to do so.
-
Security Test Case:
- Setup: Deploy the application with the default gzhttp configuration.
- Endpoint Configuration: Implement an HTTP endpoint that returns a response mixing a fixed secret (e.g. a session token) with attacker–controlled input (e.g. echoing a query parameter).
- Attack Simulation: Use a script to send numerous requests that vary only the attacker–controlled input while recording the Content–Length header (or overall response size).
- Observation: Determine if variations in the response size correlate with the attacker’s input, indicating that secret data might be leaking via compression–side channel.
- Mitigation Verification: Reconfigure the middleware to enable random–padding and verify that this correlation is no longer observable.
Insecure ZIP Archive Path Handling (Zip Slip) Vulnerability Including Inconsistent File Type Enforcement
-
Description:
- The archive/zip package accepts ZIP archives that may contain file entries whose names include relative directory–traversal sequences (e.g. "../evil.txt") or even absolute paths.
- A secure file–path validation check exists and is activated only when the environment variable
GODEBUG
is set to"zipinsecurepath=0"
. By default (or when this flag is not set), dangerous file names are accepted. - Moreover, while the higher–level API (e.g. the AddFS function) enforces safe file type checks—rejecting entries that represent symbolic links or device files (as verified in tests such as
TestIssue61875
)—lower–level APIs likeCreateHeader
do not apply such checks. - Thus, if an application uses these lower–level APIs to process ZIP archives from untrusted sources, an attacker can craft a malicious ZIP file with entries having directory–traversal paths or malicious symlink/device file attributes that bypass sanitization.
-
Impact:
- An attacker could cause directory traversal during file extraction, leading to arbitrary file creation or modification outside the intended destination.
- Such manipulation may allow an attacker to overwrite critical system or application files and, in a worst–case scenario (especially when combined with other vulnerabilities), could pave the way for arbitrary code execution or complete compromise of system integrity.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- During ZIP reader initialization (in, for example,
zip/reader.go
), a check verifies whether file names are “local” (usingfilepath.IsLocal
) and rejects names containing backslashes—but this check is only activated whenGODEBUG
is explicitly set to"zipinsecurepath=0"
. - When extracting files via the
fs.FS
interface (using theOpen
method), a helper function (such astoValidName
) sanitizes the filename so that dangerous components (e.g. "../") are stripped. - In addition, tests (e.g.
TestIssue61875
) show that when using theAddFS
API, the ZIP package enforces safe processing by rejecting dangerous file types (such as symlink and device file entries).
- During ZIP reader initialization (in, for example,
-
Missing Mitigations:
- The secure–path check is opt–in rather than enabled by default—if
GODEBUG
is not set to"zipinsecurepath=0"
, then no validation occurs. - There is no uniform enforcement across all APIs; lower–level APIs (e.g.
CreateHeader
or manual ZIP entry processing) do not automatically reject dangerous file names or enforce safe file–type checks (leaving the decision up to the developer). - The inconsistent handling means that even though the AddFS function protects against dangerous file types, developers using other APIs may inadvertently process malicious entries.
- The secure–path check is opt–in rather than enabled by default—if
-
Preconditions:
- The application accepts ZIP archives from untrusted external sources.
- The environment variable
GODEBUG
is unset or not set to"zipinsecurepath=0"
, or the developer uses lower–level APIs which bypass the secure validation. - The application subsequently uses the raw file header values (which may contain malicious names) when extracting files to disk.
-
Source Code Analysis:
- In
zip/struct.go
the file entries are represented via aFileHeader
whoseName
field is meant to be a relative path, but there is no runtime enforcement within this method. - The ZIP reader’s initialization code only performs the secure check when
GODEBUG
is set appropriately; otherwise, malicious names (containing "../" or even absolute paths) are allowed to pass through. - Test cases (e.g.
TestIssue61875
) reveal that while the AddFS API rejects ZIP entries with dangerous file modes (such as symlinks and device files), similar checks are absent when using lower–level APIs likeCreateHeader
, leaving a gap for potential exploitation.
- In
-
Security Test Case:
- Setup:
- Craft a malicious ZIP archive that contains at least one file entry with a traversal path (e.g. "../evil.txt") and optionally an entry with a symlink (for example, an entry with name "symlink" whose content is "../link/target").
- Baseline Test (Insecure Behavior):
- Ensure that the environment variable
GODEBUG
is unset (or not set to"zipinsecurepath=0"
). - Process the ZIP archive using a lower–level API (for example, by invoking
CreateHeader
) to simulate extraction from untrusted input. - Inspect the resulting
FileHeader
—observe that the file name still contains the insecure "../" component and that no file–type enforcement takes place.
- Ensure that the environment variable
- Secure Behavior Verification:
- Set the environment variable
GODEBUG
to"zipinsecurepath=0"
and reprocess the ZIP archive; confirm that the initialization detects the insecure file name and returns an error (such asErrInsecurePath
). - Alternatively, use the AddFS API and verify that it rejects dangerous file types (evidenced by an error when processing symlink or device file entries).
- Set the environment variable
- Conclusion:
- Demonstrate that without explicit enabling of secure path checks or the usage of safe APIs, an attacker can supply a ZIP archive with malicious entries, thereby enabling a Zip Slip attack during file extraction.
- Setup:
-
Description: The
RandomJitter
option ingzhttp
is intended to add padding to compressed responses to obfuscate the actual compressed size, acting as a mitigation against certain types of information leaks, such as CRIME or BREACH attacks. However, the current implementation uses a predictable seed based on a CRC32 or SHA256 checksum of the initial part of the response body ifjitterBuffer
is greater than 0, or a standardrand.Reader
ifjitterBuffer
is 0. WhenjitterBuffer
is used, this makes the padding predictable if an attacker can control or observe the beginning of the response body.Steps to trigger the vulnerability:
- An attacker needs to understand that the server is using
gzhttp
withNewWrapper
and theRandomJitter
option enabled withjitterBuffer
> 0. - The attacker crafts a request that results in a predictable initial response body content up to
jitterBuffer
size. - The server calculates the CRC32 or SHA256 of this initial part and uses it as a seed for random padding generation.
- Since the initial part of the response and the algorithm are known, the attacker can predict the padding length and content added by
RandomJitter
.
- An attacker needs to understand that the server is using
-
Impact: The predictability of the padding undermines the intended security benefit of
RandomJitter
. An attacker might still be able to infer information about the uncompressed content length or other sensitive data despite the padding, especially in scenarios where the initial part of the response is somewhat controllable or predictable. This reduces the effectiveness ofRandomJitter
as a countermeasure against compression-ratio side-channel attacks. -
Vulnerability Rank: high
-
Currently Implemented Mitigations: The
RandomJitter
option itself is intended as a mitigation, but the seed generation method is flawed whenjitterBuffer
> 0, making the padding predictable. -
Missing Mitigations: The seed for random padding generation should not depend on a predictable part of the response body when
jitterBuffer
> 0. A cryptographically secure random number generator should be used independently of the response content, or at least the seed should be derived from a source not directly influenced or observable by the attacker. ForjitterBuffer
> 0, a better approach would be to use a combination of a cryptographically secure random seed and the content hash, ensuring that even if the content is somewhat predictable, the final padding remains unpredictable to an external observer. ForjitterBuffer
== 0,rand.Reader
is used, which is acceptable. -
Preconditions:
- The server-side application uses
gzhttp
withNewWrapper
and theRandomJitter
option is enabled withjitterBuffer
> 0. - The attacker has some knowledge or can make assumptions about the initial content of the HTTP response (up to
jitterBuffer
size).
- The server-side application uses
-
Source Code Analysis: In
compress.go
, within thestartGzip
function:if len(w.randomJitter) > 0 { var jitRNG uint32 if w.jitterBuffer > 0 { if w.sha256Jitter { h := sha256.New() h.Write(w.buf) // Use only up to "w.jitterBuffer", otherwise the output depends on write sizes. if len(remain) > 0 && len(w.buf) < w.jitterBuffer { remain := remain if len(remain)+len(w.buf) > w.jitterBuffer { remain = remain[:w.jitterBuffer-len(w.buf)] } h.Write(remain) } var tmp [sha256.Size]byte jitRNG = binary.LittleEndian.Uint32(h.Sum(tmp[:0])) } else { h := crc32.Update(0, castagnoliTable, w.buf) // Use only up to "w.jitterBuffer", otherwise the output depends on write sizes. if len(remain) > 0 && len(w.buf) < w.jitterBuffer { remain := remain if len(remain)+len(w.buf) > w.jitterBuffer { remain = remain[:w.jitterBuffer-len(w.buf)] } h = crc32.Update(h, castagnoliTable, remain) } jitRNG = bits.RotateLeft32(h, 19) ^ 0xab0755de } } else { // Get from rand.Reader var tmp [4]byte _, err := rand.Read(tmp[:]) if err != nil { return fmt.Errorf("gzhttp: %w", err) } jitRNG = binary.LittleEndian.Uint32(tmp[:]) } jit := w.randomJitter[:1+jitRNG%uint32(len(w.randomJitter)-1)] w.gw.(writer.GzipWriterExt).SetHeader(writer.Header{Comment: jit}) }
- When
w.jitterBuffer > 0
, thejitRNG
is derived fromcrc32.Update
orsha256.New
based onw.buf
and potentiallyremain
(initial part of the response body). This makes the seed predictable ifw.buf
andremain
are predictable. - When
w.jitterBuffer == 0
,jitRNG
is derived fromrand.Reader
, which is cryptographically secure and not predictable based on response content.
- When
-
Security Test Case:
- Set up a test server using
gzhttp.NewWrapper
withRandomJitter(256, 1024, false)
option and a handler that returns a predictable initial response body (e.g., "PREDICTABLE_PREFIX" + random string). - Make two requests to the server that generate the same predictable prefix in the response body.
- Capture the compressed responses for both requests.
- Decompress the responses and extract the gzip header comment, which contains the jitter padding.
- Compare the jitter padding from both responses. If the vulnerability exists, the jitter padding will be the same for both responses because the predictable prefix leads to the same seed.
- If the jitter padding is identical, the test case proves the predictability of the padding and thus the vulnerability.
- Set up a test server using
-
Description: In the
Index.Load
function withinindex.go
, there are several places where integer overflows could potentially occur when reading varint-encoded values, especially forentries
,uOff
, andcOff
. While the code includes checks likev < 0
andv > maxIndexEntries
, these might not be sufficient to prevent all forms of integer overflows, especially in edge cases or on 32-bit systems. If an attacker can manipulate the index data to cause an integer overflow during loading, it could lead to unexpected behavior, incorrect offset calculations, or potentially memory corruption if these values are used as sizes or offsets in subsequent operations.Steps to trigger the vulnerability:
- An attacker needs to provide a crafted S2 index to be loaded by the
Index.Load
function. This could be achieved if the index is loaded from an external source controlled by the attacker, or if there is a vulnerability allowing modification of existing index data. - The attacker crafts the index data in a way that when varint-decoded, certain values like
entries
,uOff
, orcOff
result in integer overflows. For example, providing a very large varint value for the number of entries or offsets could trigger an overflow. - The
Index.Load
function processes this crafted data, and the integer overflow occurs. - Depending on how the overflowed value is used, it could lead to various consequences, such as incorrect index structure, out-of-bounds access when using the index, or other unexpected behavior.
- An attacker needs to provide a crafted S2 index to be loaded by the
-
Impact: Integer overflows in index loading could lead to unpredictable behavior, incorrect offset lookups, and potentially memory safety issues if overflowed values are used in memory operations. This could undermine the integrity and reliability of the index functionality and potentially the security of applications relying on it. While direct memory corruption is not guaranteed, the potential for incorrect behavior and data processing makes this a high-rank vulnerability.
-
Vulnerability Rank: high
-
Currently Implemented Mitigations: The code includes checks like
v < 0
,v > maxIndexEntries
, anduOff <= prev
during index loading, which provide some level of input validation. However, these checks may not fully prevent integer overflows, especially in edge cases or on different architectures. -
Missing Mitigations: More robust integer overflow checks should be implemented during varint decoding in
Index.Load
. Specifically, when readingentries
,uOff
, andcOff
, the code should explicitly check for potential overflows before casting toint
orint64
or performing arithmetic operations. Using libraries or built-in functions that provide overflow detection during arithmetic operations would be beneficial. For example, usingmath/bits.Add64
to check for overflow during addition of offsets. Input validation should be strengthened to reject indices with excessively large values that are likely to cause overflows. -
Preconditions:
- The application loads an S2 index using the
Index.Load
function. - The attacker can provide or influence the content of the index data being loaded.
- The application loads an S2 index using the
-
Source Code Analysis: In
index.go
, within theIndex.Load
function, multiplebinary.Varint
calls are made to read index metadata and offset values.func (i *Index) Load(b []byte) ([]byte, error) { // ... var entries int if v, n := binary.Varint(b); n <= 0 { // Reading 'entries' return b, ErrCorrupt } else { if v < 0 || v > maxIndexEntries { // Check, but overflow before this? return b, ErrCorrupt } entries = int(v) // Potential overflow during cast if v is very large b = b[n:] } // ... for idx := range i.info { var uOff int64 if hasUncompressed != 0 { // Load delta if v, n := binary.Varint(b); n <= 0 { // Reading 'uOff' delta return b, ErrCorrupt } else { uOff = v // Potential overflow if v is very large b = b[n:] } } if idx > 0 { prev := i.info[idx-1].uncompressedOffset uOff += prev + (i.estBlockUncomp) // Potential overflow during addition if uOff <= prev { // Check after addition, but overflow might have happened return b, ErrCorrupt } } if uOff < 0 { // Check after addition, but overflow might have happened return b, ErrCorrupt } i.info[idx].uncompressedOffset = uOff } // ... similar pattern for cOff ... }
- The code reads varint values for
entries
,uOff
, andcOff
without explicit checks for integer overflows before casting toint
orint64
or performing arithmetic operations. - The checks
v < 0
,v > maxIndexEntries
, anduOff <= prev
are performed after potential overflows might have already occurred during decoding or arithmetic. - On 32-bit systems or with sufficiently large crafted varint inputs, these operations could wrap around, leading to incorrect values being used for index metadata or offsets.
- The code reads varint values for
-
Security Test Case:
- Craft a malicious S2 index binary. Create a program that generates a valid S2 index structure but inserts a very large varint value for the "entries" field, close to the maximum value for a 64-bit integer.
- Prepare a test application that loads this crafted index using
Index.Load
. - Run the test application with the crafted index.
- Observe the behavior of the application. If the application exhibits unexpected behavior, crashes, or produces incorrect results when using the loaded index, it indicates a potential integer overflow vulnerability.
- Specifically, try to trigger a
Find
operation after loading the crafted index and check if it leads to out-of-bounds access or incorrect offset results due to the overflowedentries
value. - Repeat the test by crafting large varint values for
uOff
andcOff
deltas to see if overflows in offset calculations can be triggered. - Run these tests on both 64-bit and 32-bit architectures to confirm the vulnerability across different platforms.
-
Description: The
filepathx.Glob
andGlobs.Expand
functions infilepathx.go
are designed to handle glob patterns, including double-star (**
) for recursive directory traversal. While the code usesfilepath.Walk
to traverse directories, it doesn't explicitly sanitize or validate the paths returned byfilepath.Glob
or during the walk. If a malicious pattern is crafted, it might be possible to bypass intended directory boundaries and access files outside the intended scope, leading to a path traversal vulnerability. This is especially concerning iffilepathx.Glob
is used in contexts where user-provided input is directly used as a glob pattern.Steps to trigger the vulnerability:
- An attacker needs to control or influence the glob pattern passed to
filepathx.Glob
orGlobs.Expand
. This could occur if the application uses user input to construct file paths or glob patterns. - The attacker crafts a malicious glob pattern that includes path traversal sequences like
..
(e.g.,testdir/**/../../../sensitive_file
). filepathx.Glob
orGlobs.Expand
processes this pattern.- Due to the lack of explicit path sanitization,
filepath.Walk
might traverse directories outside the intended base directory, andfilepath.Glob
might match files outside the intended scope if the underlying OS and filesystem allow path traversal through symlinks or other mechanisms. - The application might then perform operations (e.g., read, compress, decompress) on these files outside the intended scope, leading to unauthorized file access.
- An attacker needs to control or influence the glob pattern passed to
-
Impact: A path traversal vulnerability could allow an attacker to access sensitive files or directories that should not be accessible. This could lead to information disclosure, unauthorized modification of files, or other security breaches, depending on how the application uses the files matched by the glob pattern. The impact is high because it allows bypassing intended access controls and potentially reading arbitrary files on the system if the application has sufficient permissions.
-
Vulnerability Rank: high
-
Currently Implemented Mitigations: The code uses standard Go functions like
filepath.Glob
andfilepath.Walk
, which provide some level of path handling safety. However, these functions alone do not prevent path traversal if the input pattern itself is malicious. There are no explicit sanitization or validation steps infilepathx.Glob
orGlobs.Expand
to prevent path traversal sequences. -
Missing Mitigations: Input validation and sanitization should be implemented in
filepathx.Glob
andGlobs.Expand
. Before processing the glob pattern or the paths returned byfilepath.Glob
andfilepath.Walk
, the code should:- Validate the glob pattern to reject patterns containing suspicious sequences like
..
. A regular expression or custom parsing logic could be used to detect and reject such patterns. - After getting the matched paths, canonicalize them using
filepath.Clean
and verify that they are still within the intended base directory. This can be done by resolving both the base directory and the matched path to their absolute forms and checking if the matched path is a prefix of the base directory. - Consider using chroot or similar sandboxing techniques if the application needs to process files from untrusted sources, to further limit the impact of path traversal vulnerabilities.
- Validate the glob pattern to reject patterns containing suspicious sequences like
-
Preconditions:
- The application uses
filepathx.Glob
orGlobs.Expand
to process file paths. - The glob pattern used in
filepathx.Glob
orGlobs.Expand
is derived from user input or an untrusted source.
- The application uses
-
Source Code Analysis: In
filepathx.go
, theGlob
andExpand
functions usefilepath.Glob
andfilepath.Walk
without additional path sanitization.// Glob adds double-star support to the core path/filepath Glob function. func Glob(pattern string) ([]string, error) { if !strings.Contains(pattern, "**") { // passthru to core package if no double-star return filepath.Glob(pattern) } return Globs(strings.Split(pattern, "**")).Expand() } // Expand finds matches for the provided Globs. func (globs Globs) Expand() ([]string, error) ([]string, error) { var matches = []string{""} // accumulate here for _, glob := range globs { var hits []string var hitMap = map[string]bool{} for _, match := range matches { paths, err := filepath.Glob(match + glob) // Potentially vulnerable call if err != nil { return nil, err } for _, path := range paths { err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { // Potentially vulnerable walk if err != nil { return err } // save deduped match from current iteration if _, ok := hitMap[path]; !ok { hits = append(hits, path) hitMap[path] = true } return nil }) if err != nil { return nil, err } } } matches = hits } return matches, nil }
- The code directly uses
filepath.Glob(match + glob)
andfilepath.Walk(path, ...)
which can be vulnerable to path traversal ifglob
ormatch
contains malicious sequences. - There are no checks to ensure that the matched paths stay within the intended directory or to sanitize the input glob patterns.
- The code directly uses
-
Security Test Case:
- Create a test directory
testdir
and within it, create a subdirectorysubdir
. Insidetestdir
, create a filesafe_file.txt
. Outsidetestdir
(e.g., in the temporary directory), create a sensitive filesensitive_file.txt
. - Write a test program that uses
filepathx.Glob
to find files based on a user-provided pattern, and then attempts to read the content of the matched files. - Provide the test program with a malicious glob pattern like
testdir/**/../../../sensitive_file.txt
. - Run the test program. If the vulnerability exists, the program will be able to access and potentially read the content of
sensitive_file.txt
outside thetestdir
directory, demonstrating path traversal. - Check if the program outputs the content of
sensitive_file.txt
or if it throws an error indicating that it could not access the file due to path restrictions. If it reads the sensitive file, the test case is successful in demonstrating the path traversal vulnerability.
- Create a test directory
-
Description: In
fse/decompress.go
andzstd/fse_decoder.go
, thereadNCount
function reads the symbol distribution from the input stream to construct decoding tables. There are checks to prevent excessively largetableLog
andsymbolLen
values. However, if a crafted input provides a largetableLog
value close totablelogAbsoluteMax
(9 inzstd/fse_decoder.go
and implicitly 15 infse/decompress.go
) and a large number of symbols, the allocated decoding tables (s.decTable
infse/decompress.go
ands.dt
inzstd/fse_decoder.go
) might become very large (up to 2^tablelogAbsoluteMax entries). If the subsequent decompression process attempts to access entries beyond the allocated size based on corrupted state or incorrect tableLog, it could lead to a buffer overflow read. This is especially relevant if theDecompressLimit
is not properly enforced or bypassed.Steps to trigger the vulnerability:
- An attacker crafts a malicious FSE compressed stream with a
tableLog
value close totablelogAbsoluteMax
and a largesymbolLen
. - The
readNCount
function infse/decompress.go
orzstd/fse_decoder.go
reads the header and allocates a potentially large decoding table (e.g.,s.decTable
of size 2^tableLog). - During the
decompress
function, due to corrupted or crafted input data following the header, the decoder state might transition to an invalid state index that is outside the bounds of the allocateds.decTable
. - When
s.decTable[state]
is accessed indecoder.next
ordecoder.nextFast
, it results in an out-of-bounds read, potentially leading to a crash or information disclosure.
- An attacker crafts a malicious FSE compressed stream with a
-
Impact: A buffer overflow read in FSE decompression tables could lead to a crash, denial of service, or potentially information disclosure if sensitive memory regions are accessed. Although it's a read overflow, it can still have security implications, especially if it can be exploited further. The vulnerability is ranked high due to the potential for crashing the application and the complexity of FSE decompression logic, which might hide further exploitable conditions.
-
Vulnerability Rank: high
-
Currently Implemented Mitigations:
- There are checks in
readNCount
to limittableLog
andsymbolLen
to prevent excessively large tables. DecompressLimit
is intended to limit the output size, but it might not directly prevent table-related buffer overflows during decompression.
- There are checks in
-
Missing Mitigations:
- More robust bounds checking should be implemented in the
decoder.next
anddecoder.nextFast
functions to ensure that the state index is always within the valid range of thes.decTable
. - Fuzzing should be performed specifically targeting the FSE decompression logic with crafted inputs that attempt to trigger out-of-bounds table accesses, especially around maximum
tableLog
andsymbolLen
values and with corrupted compressed data following the header. - Consider adding explicit size checks before accessing
s.decTable
using the table size derived fromactualTableLog
.
- More robust bounds checking should be implemented in the
-
Preconditions:
- The application uses FSE decompression on untrusted or attacker-controlled compressed data.
- The attacker can craft a malicious FSE compressed stream with specific header values and potentially corrupted data following the header.
-
Source Code Analysis: In
fse/decompress.go
andzstd/fse_decoder.go
, thereadNCount
function reads table parameters and allocatesdecTable
(ordt
). Then, indecompress
anddecoder.next/nextFast
, thedecTable
is accessed usingstate
as an index.In
fse/decompress.go
:func (s *Scratch) decompress() error { // ... var s1, s2 decoder // Initialize and decode first state and symbol. s1.init(br, s.decTable, s.actualTableLog) s2.init(br, s.decTable, s.actualTableLog) // ... for br.off >= 8 { // Main loop // ... tmp[off+0] = s1.nextFast() // Accessing s.decTable using s1.state tmp[off+1] = s2.nextFast() // Accessing s.decTable using s2.state // ... } // ... } type decoder struct { state uint16 br *bitReader dt []decSymbol // s.decTable is passed here } func (d *decoder) nextFast() uint8 { n := d.dt[d.state] // Accessing d.dt (s.decTable) using d.state lowBits := d.br.getBitsFast(n.nbBits) d.state = n.newState + lowBits return n.symbol }
Similar pattern exists in
zstd/fse_decoder.go
forfseDecoder
andfseState
. The vulnerability lies in the potential ford.state
to become out of bounds due to corrupted input, leading to an out-of-bounds read when accessingd.dt[d.state]
. -
Security Test Case:
- Craft a malicious FSE compressed stream. Create a program that generates a valid FSE header with a
tableLog
close totablelogAbsoluteMax
and a largesymbolLen
. - Append corrupted or crafted data after the header in the compressed stream. This data should be designed to manipulate the decoder state during decompression.
- Prepare a test application that uses
fse.Decompress
orzstd decoder
to decompress this crafted stream. Set a largeDecompressLimit
if necessary to avoid premature termination. - Run the test application with the crafted compressed stream.
- Monitor the application for crashes or unexpected behavior. Use memory debugging tools (like AddressSanitizer if available) to detect out-of-bounds reads during decompression.
- If a crash or memory error is detected during the table access in
decoder.next
ordecoder.nextFast
, it indicates a successful trigger of the buffer overflow read vulnerability. - Vary the crafted data and
tableLog
/symbolLen
values to explore different out-of-bounds scenarios.
- Craft a malicious FSE compressed stream. Create a program that generates a valid FSE header with a
-
Description: The
calcSkippableFrame
function inframeenc.go
calculates the size of a skippable frame to add padding. The function takeswritten
(bytes already written) andwantMultiple
(padding multiple) asint64
. While the function checks ifwantMultiple <= 0
andwritten < 0
, it does not explicitly check for integer overflows during the calculation oftoAdd
. IfwantMultiple
is very large andwritten
is also large, the calculationwantMultiple - leftOver
and subsequent additions ofwantMultiple
within the loop could potentially lead to an integer overflow, resulting in a negative or unexpectedly smalltoAdd
value. This could lead to writing a skippable frame smaller thanskippableFrameHeader
or other unexpected behavior when creating skippable frames.Steps to trigger the vulnerability:
- An attacker cannot directly trigger this vulnerability through external input to the compression library itself. However, if an application using this library allows an attacker to control or influence the
wantMultiple
parameter passed tocalcSkippableFrame
and the amount of data being written, they might be able to trigger an overflow indirectly. This is highly unlikely in most real-world scenarios where padding is controlled server-side. - An attacker needs to find a way to manipulate the
wantMultiple
andwritten
values within the application's logic that callscalcSkippableFrame
. - If
wantMultiple
andwritten
are crafted such that the calculationwantMultiple - leftOver
or subsequent additions in the loop overflow,toAdd
will become a small or negative value. - When
skippableFrame
is called with this overflowedtotal
value, it might lead to an error iftotal < skippableFrameHeader
or unexpected behavior due to the incorrect padding size.
- An attacker cannot directly trigger this vulnerability through external input to the compression library itself. However, if an application using this library allows an attacker to control or influence the
-
Impact: Integer overflow in
calcSkippableFrame
could lead to incorrect padding sizes for skippable frames, potentially causing errors during frame creation or unexpected behavior in applications relying on consistent padding. While not a direct memory corruption or information disclosure vulnerability, it can lead to denial of service if frame creation fails or unexpected application behavior. The vulnerability is ranked high due to the potential for unexpected behavior in security-sensitive padding logic, although the direct exploitability by an external attacker is low. -
Vulnerability Rank: high
-
Currently Implemented Mitigations: The function includes checks for
wantMultiple <= 0
andwritten < 0
to prevent invalid inputs, but no explicit integer overflow checks during the calculation oftoAdd
. -
Missing Mitigations: Integer overflow checks should be added to the
calcSkippableFrame
function, especially during the calculation oftoAdd
. Before addingwantMultiple
in the loop, the code should check iftoAdd + wantMultiple
would overflow theint
type. Alternatively, using a larger integer type (likeint64
) for intermediate calculations and then casting toint
with overflow checks could mitigate this issue. Input validation should be considered forwantMultiple
to ensure it is within a reasonable range that will not cause overflows in typical usage scenarios. -
Preconditions:
- The application uses
calcSkippableFrame
to calculate padding size for skippable frames. - An attacker can indirectly influence the
wantMultiple
andwritten
parameters passed tocalcSkippableFrame
through application logic.
- The application uses
-
Source Code Analysis: In
frameenc.go
, thecalcSkippableFrame
function performs calculations that are potentially vulnerable to integer overflow.// calcSkippableFrame will return a total size to be added for written // to be divisible by multiple. // The value will always be > skippableFrameHeader. // The function will panic if written < 0 or wantMultiple <= 0. func calcSkippableFrame(written, wantMultiple int64) int { if wantMultiple <= 0 { panic("wantMultiple <= 0") } if written < 0 { panic("written < 0") } leftOver := written % wantMultiple if leftOver == 0 { return 0 } toAdd := wantMultiple - leftOver // Potential overflow if wantMultiple is very large and leftOver is small for toAdd < skippableFrameHeader { toAdd += wantMultiple // Potential overflow in repeated addition } return int(toAdd) }
- The lines
toAdd := wantMultiple - leftOver
andtoAdd += wantMultiple
in the loop are potential locations for integer overflows ifwantMultiple
is sufficiently large. - The return value
int(toAdd)
is a cast fromint64
toint
, which could also truncate or wrap around iftoAdd
is outside the valid range ofint
.
- The lines
-
Security Test Case:
- Create a test program that calls
calcSkippableFrame
with very largewantMultiple
values (close tomath.MaxInt64
) and varyingwritten
values to try to trigger an integer overflow in thetoAdd
calculation. - Run the test program and check the returned
toAdd
values. IftoAdd
becomes negative or unexpectedly small when large inputs are used, it indicates an integer overflow vulnerability. - Specifically, check if
skippableFrame
called with the potentially overflowedtoAdd
value results in an error due tototal < skippableFrameHeader
or if it creates a skippable frame with an invalid size. - Monitor the behavior of the application and check for any panics or unexpected results caused by the overflowed padding size.
- Create a test program that calls
-
Description: The assembly optimized
buildDtable_asm
function inzstd/fse_decoder_amd64.go
for building FSE decoding tables might contain vulnerabilities if not carefully implemented. Specifically, if the assembly code incorrectly calculates table indices or performs memory accesses, it could lead to out-of-bounds reads from thes.norm
ors.stateTable
arrays, or writes tos.dt
outside its allocated bounds. While Go code provides memory safety, assembly code can bypass these checks if not written correctly. A crafted compressed stream designed to exploit potential flaws in the assembly logic could trigger an out-of-bounds read, potentially leading to information disclosure or crashes.Steps to trigger the vulnerability:
- An attacker crafts a malicious FSE compressed stream with specific header values designed to trigger a specific code path in the
buildDtable_asm
function. - The crafted header might aim to manipulate
tableLog
,symbolLen
, or the normalized counts (s.norm
) in a way that causes the assembly code to calculate invalid memory addresses. - During the execution of
buildDtable_asm
, an out-of-bounds read occurs when accessings.norm
,s.stateTable
, or when writing tos.dt
due to incorrect index calculations in the assembly code. - This out-of-bounds read could potentially leak sensitive information from process memory or cause a crash, depending on the nature and location of the read.
- An attacker crafts a malicious FSE compressed stream with specific header values designed to trigger a specific code path in the
-
Impact: An out-of-bounds read in the assembly optimized FSE decoding table construction could lead to information disclosure, denial of service (crash), or potentially further exploitation if the read data can be controlled or observed. The vulnerability is ranked high due to the potential for serious security impacts and the complexity of analyzing assembly code for memory safety issues, especially in the context of a complex decompression algorithm.
-
Vulnerability Rank: high
-
Currently Implemented Mitigations: The Go code surrounding the assembly function (
fseDecoder.buildDtable
) includes some checks and type safety, but these may not fully prevent vulnerabilities in the assembly implementation itself. ThereadNCount
function performs checks ontableLog
andsymbolLen
, but the assembly code might still have logical flaws leading to out-of-bounds accesses within the allowed table sizes. -
Missing Mitigations:
- Thorough manual review and verification of the
buildDtable_asm
assembly code are necessary to ensure memory safety and correctness, especially concerning index calculations and bounds checking. - Automated tools for assembly code analysis or symbolic execution could be used to detect potential out-of-bounds read vulnerabilities.
- Fuzzing should be performed with crafted FSE compressed streams specifically targeting the assembly optimized decoding path to attempt to trigger out-of-bounds reads during table construction. Memory sanitizers (like AddressSanitizer) should be used during fuzzing to detect memory access violations.
- Add assertions within the assembly code (if feasible and without significant performance impact) to check for valid memory access ranges before performing reads or writes to the decoding tables.
- Thorough manual review and verification of the
-
Preconditions:
- The application uses Zstandard decompression on an AMD64 architecture where assembly optimizations are enabled (
amd64 && !appengine && !noasm && gc
build tag). - The attacker can provide a crafted Zstandard compressed stream that utilizes FSE encoding for literals or sequences and is designed to trigger the vulnerable code path in
buildDtable_asm
.
- The application uses Zstandard decompression on an AMD64 architecture where assembly optimizations are enabled (
-
Source Code Analysis: In
zstd/fse_decoder_amd64.go
, thebuildDtable_asm
function is called byfseDecoder.buildDtable
. The assembly code (not provided in the Go file itself, but would be in a separate.s
file or generated) is responsible for building the decoding tables.dt
based on the normalized counts ins.norm
and state tables.stateTable
.// buildDtable will build the decoding table. func (s *fseDecoder) buildDtable() error { ctx := buildDtableAsmContext{ stateTable: &s.stateTable[0], norm: &s.norm[0], dt: (*uint64)(&s.dt[0]), } code := buildDtable_asm(s, &ctx) if code != 0 { // ... error handling ... } return nil }
The vulnerability would reside within the
buildDtable_asm
assembly implementation. Without access to the assembly source code, static analysis is limited. However, potential areas of concern include:- Index calculations: Incorrectly calculated indices when accessing
s.norm
,s.stateTable
, ors.dt
based ontableSize
,highThreshold
,step
,position
, or loop variables. - Memory access patterns: Flaws in how memory addresses are derived and accessed in assembly, potentially leading to reads outside the intended bounds of the allocated tables.
- State management: Errors in managing the decoder state during table construction in assembly, causing incorrect table entries or out-of-bounds accesses in subsequent decoding stages.
- Index calculations: Incorrectly calculated indices when accessing
-
Security Test Case:
- Craft a malicious Zstandard compressed stream. Create a program that generates a Zstandard stream with an FSE encoded block. The FSE header should be crafted with specific
tableLog
andsymbolLen
values, and the compressed data following the header should be designed to trigger the assembly optimizedbuildDtable_asm
function. - Prepare a test application that uses the Go Zstandard decoder to decompress this crafted stream. Ensure the application is built and run on an AMD64 architecture with assembly optimizations enabled.
- Run the test application with the crafted compressed stream.
- Monitor the application for crashes or unexpected behavior. Use memory debugging tools like AddressSanitizer (if available and compatible with assembly code) or Valgrind to detect out-of-bounds memory reads or writes during the execution of
buildDtable_asm
. - If a crash or memory error is detected within
buildDtable_asm
or during table access after table construction, it indicates a successful trigger of the out-of-bounds read vulnerability. - Vary the crafted header values and compressed data to explore different code paths in the assembly function and attempt to trigger different out-of-bounds scenarios.
- Craft a malicious Zstandard compressed stream. Create a program that generates a Zstandard stream with an FSE encoded block. The FSE header should be crafted with specific
-
Description: The
fastGen.addBlock
function infast_encoder.go
manages a history buffer (e.hist
) to store previously processed data for efficient compression. When the buffer is full, it attempts to move the lastmaxMatchOffset
bytes to the beginning of the buffer using unsafe pointer casting and slice manipulation. If the length ofe.hist
is less thanmaxMatchOffset
at the time of buffer reset, the memory copy operation*(*[maxMatchOffset]byte)(e.hist) = *(*[maxMatchOffset]byte)(e.hist[offset:])
could lead to an out-of-bounds write, as it attempts to writemaxMatchOffset
bytes into a potentially smaller slice. This could lead to memory corruption.Steps to trigger the vulnerability:
- Craft an input stream that causes the
fastGen.addBlock
function to be called repeatedly, filling up the history buffere.hist
. - Ensure that at some point during the compression process, the length of
e.hist
becomes less thanmaxMatchOffset
due to specific compression logic or input patterns (this scenario needs further investigation to reliably trigger). - Provide more input data that triggers the buffer reset condition in
addBlock
(len(e.hist) + len(src) > cap(e.hist)
). - When the code reaches the memory copy
*(*[maxMatchOffset]byte)(e.hist) = *(*[maxMatchOffset]byte)(e.hist[offset:])
withlen(e.hist) < maxMatchOffset
, an out-of-bounds write occurs.
- Craft an input stream that causes the
-
Impact: An out-of-bounds write in
fastGen.addBlock
can lead to memory corruption, potentially causing crashes, unpredictable behavior, or exploitable conditions. While direct external exploitability might be low, memory corruption vulnerabilities are generally considered high severity due to their potential security implications. -
Vulnerability Rank: high
-
Currently Implemented Mitigations: The code includes a check
if cap(e.hist) < maxMatchOffset*2
which is intended to prevent unexpected buffer sizes, but it does not directly prevent the out-of-bounds write iflen(e.hist)
is less thanmaxMatchOffset
during the copy operation. -
Missing Mitigations:
- Before performing the memory copy in
fastGen.addBlock
, ensure thatlen(e.hist)
is always at leastmaxMatchOffset
. If it's not, handle this case gracefully, possibly by allocating a new buffer or adjusting the copy logic to avoid out-of-bounds access. - Add explicit bounds checks before the memory copy to verify that the source and destination ranges are within the allocated memory for
e.hist
. - Thoroughly review the code paths that lead to
fastGen.addBlock
to understand under what conditionslen(e.hist)
could become less thanmaxMatchOffset
before a buffer reset, and ensure these scenarios are handled safely. - Fuzzing with diverse input data, especially edge cases and boundary conditions, should be performed to try and trigger this potential out-of-bounds write. Memory sanitizers should be used during fuzzing to detect memory access violations.
- Before performing the memory copy in
-
Preconditions:
- The application uses flate compression with fast encoding levels (1-6).
- The attacker can provide input data that is processed by the flate encoder, potentially influencing the state of the
fastGen
history buffer.
-
Source Code Analysis: In
flate/fast_encoder.go
, thefastGen.addBlock
function contains the potentially vulnerable memory copy operation:func (e *fastGen) addBlock(src []byte) int32 { // ... else { if cap(e.hist) < maxMatchOffset*2 { panic("unexpected buffer size") } // Move down offset := int32(len(e.hist)) - maxMatchOffset // copy(e.hist[0:maxMatchOffset], e.hist[offset:]) *(*[maxMatchOffset]byte)(e.hist) = *(*[maxMatchOffset]byte)(e.hist[offset:]) // Potential out-of-bounds write e.cur += offset e.hist = e.hist[:maxMatchOffset] } // ... }
- The line
*(*[maxMatchOffset]byte)(e.hist) = *(*[maxMatchOffset]byte)(e.hist[offset:])
performs a direct memory copy using unsafe pointer casting. - If
len(e.hist)
is less thanmaxMatchOffset
, thene.hist[:maxMatchOffset]
would attempt to access memory beyond the bounds of the allocated slice, resulting in an out-of-bounds write. - The condition
if cap(e.hist) < maxMatchOffset*2
is a precondition, but it does not guarantee thatlen(e.hist)
will always be >=maxMatchOffset
when the copy is performed.
- The line
-
Security Test Case:
- Create a test program that uses the flate compressor with a fast encoding level (e.g., BestSpeed).
- Construct a crafted input byte stream that is designed to manipulate the state of the
fastGen
history buffer and potentially reduce its length belowmaxMatchOffset
before triggering a buffer reset. (This step might require some experimentation and understanding of the flate compression algorithm and encoder state management.) - Compress the crafted input using the flate writer.
- Run the test program with memory sanitizers (like AddressSanitizer) enabled to detect out-of-bounds writes during the compression process.
- If AddressSanitizer reports an out-of-bounds write within the
fastGen.addBlock
function, it confirms the vulnerability. - Vary the crafted input to explore different scenarios and potentially refine the test case to reliably trigger the vulnerability.