From 57cbbc09e41ead549b37ac0c2ecaed6fb84b21ba Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 18 Dec 2025 18:04:28 -0800 Subject: [PATCH] fix: correct off-by-one bounds checks in bundler and package installer (#25582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix two off-by-one bounds check errors that used `>` instead of `>=` - Both bugs could cause undefined behavior (array out-of-bounds access) when an index equals the array length ## The Bugs ### 1. `src/install/postinstall_optimizer.zig:62` ```zig // Before (buggy): if (resolution > metas.len) continue; const meta: *const Meta = &metas[resolution]; // Out-of-bounds when resolution == metas.len // After (fixed): if (resolution >= metas.len) continue; ``` ### 2. `src/bundler/linker_context/doStep5.zig:10` ```zig // Before (buggy): if (id > c.graph.meta.len) return; const resolved_exports = &c.graph.meta.items(.resolved_exports)[id]; // Out-of-bounds when id == c.graph.meta.len // After (fixed): if (id >= c.graph.meta.len) return; ``` ## Why These Are Bugs Valid array indices are `0` to `len - 1`. When `index == len`: - `index > len` evaluates to `false` → check passes - `array[index]` accesses `array[len]` → out-of-bounds / undefined behavior ## Codebase Patterns The rest of the codebase correctly uses `>=` for these checks: - `lockfile.zig:484`: `if (old_resolution >= old.packages.len) continue;` - `lockfile.zig:522`: `if (old_resolution >= old.packages.len) continue;` - `LinkerContext.zig:389`: `if (source_index >= import_records_list.len) continue;` - `LinkerContext.zig:1667`: `if (source_index >= c.graph.ast.len) {` ## Test plan - [x] Verified fix aligns with existing codebase patterns - [ ] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- src/bundler/linker_context/doStep5.zig | 2 +- src/install/postinstall_optimizer.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundler/linker_context/doStep5.zig b/src/bundler/linker_context/doStep5.zig index a1df034bac..a2c55e745e 100644 --- a/src/bundler/linker_context/doStep5.zig +++ b/src/bundler/linker_context/doStep5.zig @@ -7,7 +7,7 @@ pub fn doStep5(c: *LinkerContext, source_index_: Index, _: usize) void { defer trace.end(); const id = source_index; - if (id > c.graph.meta.len) return; + if (id >= c.graph.meta.len) return; const worker: *ThreadPool.Worker = ThreadPool.Worker.get(@fieldParentPtr("linker", c)); defer worker.unget(); diff --git a/src/install/postinstall_optimizer.zig b/src/install/postinstall_optimizer.zig index d2ed846fb8..588d60ca5b 100644 --- a/src/install/postinstall_optimizer.zig +++ b/src/install/postinstall_optimizer.zig @@ -59,7 +59,7 @@ pub const PostinstallOptimizer = enum { // Loop through the list of optional dependencies with platform-specific constraints // Find a matching target-specific dependency. for (resolutions) |resolution| { - if (resolution > metas.len) continue; + if (resolution >= metas.len) continue; const meta: *const Meta = &metas[resolution]; if (meta.arch == .all or meta.os == .all) continue; if (meta.arch.isMatch(target_cpu) and meta.os.isMatch(target_os)) {