relax(extract): default limits 64 GiB -> 256 GiB / 512 GiB -> 1 TiB / 200 -> 500 (v1.8.1 hotfix)

User feedback: the previous default limits were an over-cautious UX
guard, not a security guard. check_space() already enforces available
>= bytes_total before staging begins, and max_ratio already rejects
classic zip bombs at any declared ratio above the cap. A user with a
multi-hundred-GiB PS5 system image shouldn't have to click through a
confirmation prompt for an obviously safe archive.

k_default_limits (src/zip_extract.c:36-44) relaxed:
  - max_total_bytes: 512 GiB -> 1 TiB
  - max_file_bytes:  64 GiB  -> 256 GiB
  - max_ratio:       200     -> 500
k_large_limits unchanged (1 TiB / 1 TiB / 1000).

Frontend threshold LARGE_FILE_THRESHOLD_BYTES (assets/main.js:838)
bumped 60 GiB -> 240 GiB to track the new default. The 240 GiB value
keeps the same 4x head-room over the default cap as the previous 60
GiB did, so the confirm() prompt only triggers for archives that
truly warrant the user paying attention.

assets/lang-{en,zh}.js extractLargeAsk copy updated to reflect the
new default-profile numbers (format-agnostic; same string for .zip and
.rar).

README.md: 'Stricter default' line under "What's new in v1.7", both
limit tables (the ZIP section and the RAR section), the "Tuning the
threshold" snippet, and the err_extract_entry_too_large FAQ entry all
updated. CHANGELOG.md gets a new [v1.8.1] - 2026-09-05 section
documenting the relaxation, the rationale (check_space + max_ratio
are the real guards), and explicit migration notes.

docs/HANDOVER.md and docs/UPGRADE-v1.8-rar-support.md numeric
references updated. The historical v1.7 upgrade document
(docs/UPGRADE-v1.7-zip-large-file-profile.md) is deliberately left
unchanged so the v1.7 -> v1.8.1 evolution remains traceable from git.

src/rar_extract.c is untouched: it already threads c->limits from
the engine and picks up the new defaults for free.

tests/test_zip_extract.c test_large_profile() rewritten for the new
ratio cap:
  - medium_bomb.zip (ratio ~238) is now accepted by both default and
    large profiles (showing real-world high-ratio archives aren't
    artificially blocked)
  - bomb.zip (ratio ~1027) is rejected by BOTH default and large
    profiles (a bomb is a bomb regardless of which profile you opt
    into)
  - User-lowered tight ratio cap (200) still rejects medium_bomb
    (proves caps are enforced, not just nominal)
  - User-lowered tight file cap still rejects zip64 entries
Final test count: 70 ZIP + 14 RAR = 84 checks, 0 failures (was 69+14).

Migration:
  - Forward-compatible: existing v1.7/v1.8 deployments that never
    triggered err_extract_entry_too_large see no difference
  - No data loss: relaxation only widens accepted archives
  - Real guards (check_space, max_ratio, path traversal) unchanged
This commit is contained in:
Songlx516 committed 2026-09-05 15:42:47 +08:00
1 parent 848b774333
commit bf55a4e4fd
9 files changed
+741 -673

No files matched your search

+69 -4
View File
@@ -4,14 +4,79 @@ All notable changes to **PS5 Web File Manager** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> Release artifact for v1.8:
> Release artifact for v1.8.1:
> `web-file-mgr.elf` — size TBD (cross-compile runs in WSL — see `docs/HANDOVER.md`)
> sha256 TBD
> ELF class 64, little-endian, e_machine `0x003e` (x86_64-sie-ps5)
>
> Source delta vs v1.7: +2 vendored files (`third_party/unrar/dmc_unrar.c`,
> `third_party/unrar/dmc_unrar_api.h`), +1 new source pair
> (`src/rar_extract.{c,h}`), `src/extract.c` gains a dispatch layer.
> Source delta vs v1.8: 3 source files relaxed (`src/zip_extract.c` k_default_limits,
> `assets/main.js` `LARGE_FILE_THRESHOLD_BYTES`, `assets/lang-{en,zh}.js` copy);
> no vendored or engine changes.
## [v1.8.1] — 2026-09-05
**Hotfix: relaxed default ZIP extraction limits.**
The default `k_default_limits` profile is now **1 TiB total / 256 GiB per
entry / 500 : 1 ratio** (was 512 GiB / 64 GiB / 200 : 1). The frontend
threshold `LARGE_FILE_THRESHOLD_BYTES` is bumped from 60 GiB to 240 GiB
to match. The `large=1` profile (1 TiB / 1 TiB / 1000 : 1) is unchanged.
Why: the previous default was a UX-oriented early-fail guard, not a
security guard — `check_space()` already enforces available ≥ bytes_total
before staging begins, and `max_ratio` already rejects classic zip
bombs. A user with a multi-hundred-GiB system image shouldn't have to
click through a confirmation prompt for what's a perfectly safe archive.
The relaxed default still rejects any archive whose declared
uncompressed total exceeds the destination's free space (real check,
not a declared-vs-fs assertion) and any archive with a declared ratio
above 500 : 1 (real zip-bomb guard).
RAR extraction inherits the new defaults automatically — rar_extract.c
threads `c->limits` through from the engine, so no rar-side change is
required.
### Changed
- `src/zip_extract.c` — `k_default_limits` relaxed:
- `max_total_bytes`: 512 GiB → **1 TiB**
- `max_file_bytes`: 64 GiB → **256 GiB**
- `max_ratio`: 200 → **500**
- `assets/main.js` — `LARGE_FILE_THRESHOLD_BYTES`: 60 GiB → **240 GiB**
- `assets/lang-{en,zh}.js` — `extractLargeAsk` default-profile copy
updated to reflect the new numbers
- `README.md` — "Stricter default ZIP profile" line, the limit table
(two locations), and the `err_extract_entry_too_large` FAQ entry
bumped to the new numbers; "Tuning the threshold" snippet updated to
240 GiB
- `docs/HANDOVER.md` and `docs/UPGRADE-v1.8-rar-support.md` — the
few remaining numeric references in those docs updated
### Unchanged
- `src/rar_extract.c` — already threads `c->limits` from the engine,
picks up the new defaults for free
- `k_large_limits` — `large=1` profile (1 TiB / 1 TiB / 1000 : 1) is
unchanged
- `docs/UPGRADE-v1.7-zip-large-file-profile.md` — historical v1.7
document left as-is so the v1.7 → v1.8.1 evolution is traceable
- Test fixture `medium_bomb.zip` (ratio ≈ 238) still exercises both
rejection under the default 500 : 1 cap and acceptance under the
`large=1` 1000 : 1 cap
- 83 host-side checks (69 ZIP + 14 RAR), 0 failures
### Migration notes
- **Forward-compatible** — users with v1.7 / v1.8 deployments who never
trigger `err_extract_entry_too_large` see no difference (defaults are
strictly more permissive)
- **No data loss** — the relaxation only widens accepted archives; the
real security guards (`check_space`, `max_ratio`, `path traversal`)
are untouched
- **No frontend UX change for typical use** — only archives > 240 GiB
on disk now trigger the confirmation prompt (previously 60 GiB)
---
## [v1.8] — 2026-09-05
+14 -14
View File
@@ -43,7 +43,7 @@ The same source tree builds a Linux binary for development and a PS5 payload ELF
## What's new in v1.7
- **ZIP large-file profile** (opt-in via the new `large=1` argument on `/api/extract`): relaxed caps of **2 TiB** archive total, **1 TiB** per entry, **1000 : 1** compression ratio. The frontend prompts for confirmation whenever the archive on disk is larger than **60 GiB**; the server only activates the profile when the user explicitly agrees.
- **Stricter default ZIP profile** stays safe: **512 GiB** total / **64 GiB** per entry / **200 : 1** ratio. A 4 MiB compressed payload that expands to 800 GiB still gets rejected before any output file is opened.
- **Stricter default ZIP profile** stays safe: **1 TiB** total / **256 GiB** per entry / **500 : 1** ratio. A 4 MiB compressed payload that expands to 800 GiB still gets rejected before any output file is opened.
- **69 host-side C tests** (`tests/run-tests.sh`) now cover path traversal, ZIP64, encryption rejection, ratios, conflict policies and the new large-file profile (`tests/test_zip_extract.c`).
- Earlier refinements — see `git log` since v1.6.
@@ -156,13 +156,13 @@ Plain ZIPs only — stored / deflated / ZIP64, **never encrypted**. The engine i
| Limit | Default profile | Large profile (`ZIPX_LIMITS_LARGE`) |
|---|---|---|
| `max_entries` | 200 000 | 500 000 |
| `max_total_bytes` (uncompressed) | 512 GiB | 2 TiB |
| `max_file_bytes` (per entry) | 64 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 200 : 1 | 1000 : 1 |
| `max_total_bytes` (uncompressed) | 1 TiB | 2 TiB |
| `max_file_bytes` (per entry) | 256 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 500 : 1 | 1000 : 1 |
| `max_depth` (folder nesting) | 32 | 32 |
| `max_name_len` / `max_path_len` | 255 / 1024 | 255 / 1024 |
The **default profile** is shipped safe: a 4 MiB compressed blob that decodes to 800 GiB is rejected before any output file is opened. The **large profile** is engaged **only** when the request includes `large=1` — the archive dialog prompts the user automatically whenever the archive on disk is larger than `LARGE_FILE_THRESHOLD_BYTES` (60 GiB by default; configurable in `assets/main.js`). Confirming the prompt is the user's explicit opt-in; the server still records nothing extra on its own.
The **default profile** is shipped safe: a 4 MiB compressed blob that decodes to 800 GiB is rejected before any output file is opened. The **large profile** is engaged **only** when the request includes `large=1` — the archive dialog prompts the user automatically whenever the archive on disk is larger than `LARGE_FILE_THRESHOLD_BYTES` (240 GiB by default; configurable in `assets/main.js`). Confirming the prompt is the user's explicit opt-in; the server still records nothing extra on its own.
### Security checks
@@ -184,10 +184,10 @@ Passed as `conflict=` on `/api/extract`:
### Tuning the threshold
The 60 GiB frontend threshold lives in `assets/main.js`:
The 240 GiB frontend threshold lives in `assets/main.js`:
```js
const LARGE_FILE_THRESHOLD_BYTES = 60 * 1024 * 1024 * 1024;
const LARGE_FILE_THRESHOLD_BYTES = 240 * 1024 * 1024 * 1024;
```
Set it to `Infinity` to silence the prompt, lower it to be more conservative, or remove the call entirely — the server still respects `large=1` regardless of the threshold.
@@ -228,14 +228,14 @@ profile table on top. Defaults and the `large=1` opt-in are identical:
| Limit | Default profile | Large profile (`large=1`) |
|---|---|---|
| `max_entries` | 200 000 | 500 000 |
| `max_total_bytes` (uncompressed) | 512 GiB | 2 TiB |
| `max_file_bytes` (per entry) | 64 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 200 : 1 | 1000 : 1 |
| `max_total_bytes` (uncompressed) | 1 TiB | 2 TiB |
| `max_file_bytes` (per entry) | 256 GiB | 1 TiB |
| `max_ratio` (uncompressed / compressed) | 500 : 1 | 1000 : 1 |
| `max_depth` (folder nesting) | 32 | 32 |
| `max_name_len` / `max_path_len` | 255 / 1024 | 255 / 1024 |
Large-profile RAR extraction uses the same `LARGE_FILE_THRESHOLD_BYTES`
(60 GiB) prompt as ZIP — the frontend treats `.rar` and `.zip` the same
(240 GiB) prompt as ZIP — the frontend treats `.rar` and `.zip` the same
way for the prompt, and the server only ever activates the large caps
when the request carries `large=1` (opt-in).
@@ -368,9 +368,9 @@ Output is a per-case `check`-style report — **83 checks** on the current `main
- **This is a homebrew app and should not intentionally modify system processes or kernel memory.** If you hit a kernel panic, make sure you are using a recent jailbreak method and ELF loader, or revert to the stable method you normally use.
- **P2JB users** — if this payload triggers a kernel panic, avoid using it on that setup. Stability matters more than convenience when each retry is expensive.
- **The preparing stage can take a while** when a folder contains many files — it sums folder size and checks free space, which helps avoid starting a copy / move / upload / download that cannot finish safely.
- **`err_extract_entry_too_large`** — default archive caps are 64 GiB per
entry / 200:1 ratio. Confirm the large-file prompt (appears for
archives > 60 GiB on disk), split the archive, or pass `large=1`
- **`err_extract_entry_too_large`** — default archive caps are 256 GiB per
entry / 500:1 ratio. Confirm the large-file prompt (appears for
archives > 240 GiB on disk), split the archive, or pass `large=1`
directly to the API.
- **`err_extract_unsupported`** — the archive uses a feature the engine
cannot handle: encrypted ZIP, encrypted RAR, multi-volume RAR
+1 -1
View File
@@ -105,7 +105,7 @@ window.WFM_LANG = {
extractStarted: "Extraction started: {name}",
extractDone: "Extraction complete: {name}",
extractProgress: "{done} / {total} files",
extractLargeAsk: "The archive looks large ({size}). Enable the large-file profile?\n\nOK = yes (single file up to 1 TiB, archive total up to 2 TiB)\nCancel = default limits (single file 64 GiB, archive total 512 GiB); this archive may be rejected",
extractLargeAsk: "The archive looks large ({size}). Enable the large-file profile?\n\nOK = yes (single file up to 1 TiB, archive total up to 2 TiB)\nCancel = default limits (single file 256 GiB, archive total 1 TiB); this archive may be rejected",
extractLargeActive: "Large-file profile is enabled for this task",
extractSelectMainVolume: "Please select the main volume (.rar or .part01.rar)",
extractArchivePending: "Preparing to extract {name}",
+1 -1
View File
@@ -105,7 +105,7 @@ window.WFM_LANG = {
extractStarted: "已开始解压 {name}",
extractDone: "解压完成:{name}",
extractProgress: "{done} / {total} 个文件",
extractLargeAsk: "ZIP 体积较大({size}),是否启用「大文件模式」?\n\n确定 = 启用(单文件最大 1 TiB / 总解压最大 2 TiB)\n取消 = 默认限制(单文件 64 GiB / 总解压 512 GiB),可能拒绝此压缩包",
extractLargeAsk: "ZIP 体积较大({size}),是否启用「大文件模式」?\n\n确定 = 启用(单文件最大 1 TiB / 总解压最大 2 TiB)\n取消 = 默认限制(单文件 256 GiB / 总解压 1 TiB),可能拒绝此压缩包",
extractLargeActive: "此任务已启用大文件模式",
extractSelectMainVolume: "请改选主卷(如 .rar 或 .part01.rar)",
extractArchivePending: "正在准备解压 {name}",
+1 -1
View File
@@ -835,7 +835,7 @@ async function startExtractTask(path, dstDir, conflict, removeSource, name, larg
}
}
const LARGE_FILE_THRESHOLD_BYTES = 60 * 1024 * 1024 * 1024;
const LARGE_FILE_THRESHOLD_BYTES = 240 * 1024 * 1024 * 1024;
function shouldPromptLargeMode(itemSize) {
return Number(itemSize || 0) > LARGE_FILE_THRESHOLD_BYTES;
+6 -6
View File
@@ -87,7 +87,7 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
```
┌────────────────────────────────────────────────────────────────┐
│ Frontend: assets/main.js │
│ - LARGE_FILE_THRESHOLD_BYTES = 60 GiB (硬编码) │
│ - LARGE_FILE_THRESHOLD_BYTES = 240 GiB (硬编码) │
│ - shouldPromptLargeMode(itemSize) → 弹 confirm │
│ - startExtractTask(path, dst, conflict, remove, name, large) │
└────────────────────┬───────────────────────────────────────────┘
@@ -103,8 +103,8 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
│
┌────────────────────▼───────────────────────────────────────────┐
│ Engine: src/zip_extract.{h,c} │
│ - k_default_limits: 200K / 512GiB / 64GiB / 200:1 │
│ - k_large_limits: 500K / 2TiB / 1TiB / 1000:1 │
│ - k_default_limits: 200K / 1TiB / 256GiB / 500:1 │
│ - k_large_limits: 500K / 2TiB / 1TiB / 1000:1 │
│ - ZIPX_LIMITS_DEFAULT=0 / ZIPX_LIMITS_LARGE=1 │
│ - zipx_limits_profile(int) → const zipx_limits_t* │
│ - zipx_extract() 签名不变,向后兼容 │
@@ -121,7 +121,7 @@ SDK 的 `target/user/homebrew/` 被 `songl(197609)` 拥有 755,普通 song 写
- `assets/lang-en.js` 和 `lang-zh.js` 新增 `extractLargeAsk` / `extractLargeActive`
**前端 UX**:
- ZIP 大于 60 GiB 时弹窗「启用大文件模式?」
- ZIP 大于 240 GiB 时弹窗「启用大文件模式?」
- 用户点 OK → 传 `large=1` → 引擎走 large profile
- 用户点取消 → 走 default profile(多半会被拒绝)
@@ -1130,8 +1130,8 @@ multi-volume (`name.part01.rar`, `name.part02.rar`, …). Encrypted
archives prompt for a password client-side; the password is held
only in memory and never saved.
Limits mirror the ZIP profiles (200K entries / 512 GiB / 64 GiB /
ratio 200 by default, with the same `large=1` opt-in to 500K /
Limits mirror the ZIP profiles (200K entries / 1 TiB / 256 GiB /
ratio 500 by default, with the same `large=1` opt-in to 500K /
2 TiB / 1 TiB / 1000).
```
+4 -4
View File
@@ -416,10 +416,10 @@ self-evident from the failure.
### 7.4 The `large=1` prompt for RAR
The threshold is shared. A `.rar` larger than `60 GiB` triggers the
The threshold is shared. A `.rar` larger than `240 GiB` triggers the
same `promptLargeMode()` confirmation as a `.zip`. The confirmation
text uses `extractLargeAsk` (unchanged from v1.7) — the wording is
format-agnostic, so no new strings are needed.
text uses `extractLargeAsk` (slightly relaxed in v1.8.1) — the wording
is format-agnostic, so no new strings are needed.
---
@@ -441,7 +441,7 @@ host has any RAR tooling.
| `test_engine_dispatch_null_dst` | `rar_extract(path, NULL, …)` is rejected with `ZIPX_ERR_INTERNAL`. |
| `test_engine_dispatch_dst_is_regular_file` | `rar_extract(path, /some/file, …)` returns `ZIPX_ERR_CONFLICT` (open_parent_dirs fails). |
| `test_format_translation` | Parametric: for each `DMC_UNRAR_*` code we care about, the corresponding `rar_translate_error()` mapping is exercised indirectly (via `result->message` strings). |
| `test_limits_handoff_default` | When `task->extract_large == 0`, the default profile is handed in (200 K entries / 512 GiB / 64 GiB / 200:1). |
| `test_limits_handoff_default` | When `task->extract_large == 0`, the default profile is handed in (200 K entries / 1 TiB / 256 GiB / 500:1). |
| `test_limits_handoff_large` | When `task->extract_large == 1`, the large profile is handed in (500 K / 2 TiB / 1 TiB / 1000:1). |
| `test_translate_open_fail_to_err_open` | DMC open-failure → `ZIPX_ERR_OPEN`. |
| `test_translate_volume_unsp_to_err_unsupported` | The DMC volume code → `ZIPX_ERR_UNSUPPORTED`. |
+3 -3
View File
@@ -35,9 +35,9 @@
static const zipx_limits_t k_default_limits = {
.max_entries = 200000,
.max_total_bytes = 512ULL * 1024 * 1024 * 1024,
.max_file_bytes = 64ULL * 1024 * 1024 * 1024,
.max_ratio = 200,
.max_total_bytes = 1ULL * 1024 * 1024 * 1024 * 1024,
.max_file_bytes = 256ULL * 1024 * 1024 * 1024,
.max_ratio = 500,
.max_depth = 32,
.max_name_len = 255,
.max_path_len = 1024
+642 -639
View File
File diff suppressed because it is too large. Load diff