fix: correct off-by-one bounds checks in bundler and package installer (#25582)

## 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 <noreply@anthropic.com>
This commit is contained in:
Dylan Conway
2025-12-18 18:04:28 -08:00
committed by GitHub
parent 7f589ffb4b
commit 57cbbc09e4
2 changed files with 2 additions and 2 deletions

View File

@@ -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();

View File

@@ -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)) {