Skip to content

[Repo Assist] fix: clamp INT_MIN dynamic width in l_vsnprintf to avoid UB#149

Draft
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/fix-vsnprintf-width-intmin-e885e4126c4fac3e
Draft

[Repo Assist] fix: clamp INT_MIN dynamic width in l_vsnprintf to avoid UB#149
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/fix-vsnprintf-width-intmin-e885e4126c4fac3e

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

Fix undefined behaviour when INT_MIN is passed as a dynamic field width in l_vsnprintf (via %*d and similar specifiers).

Root Cause

When a negative value is passed as a * width argument, l_vsnprintf sets flag_minus = 1 and then negates the width:

if (width < 0) { flag_minus = 1; width = -width; }

If width == INT_MIN, the expression -INT_MIN is undefined behaviour in C (signed integer overflow). On x86 hardware the result wraps back to INT_MIN, which is still negative, making the subsequent padding calculation overflow too and returning a large negative value from l_snprintf.

Fix

l_os.h — one-line guard:

width = (width == INT_MIN) ? INT_MAX : -width;
```

This clamps the pathological value to `INT_MAX`, giving a well-defined (if impractically large) left-justified field. All subsequent arithmetic stays within `int` range and the return value from `l_snprintf` is `INT_MAX` (positive).

## Trade-offs

- Any caller passing `INT_MIN` as a width was already getting garbagethis change makes the output well-defined instead.
- `INT_MAX` padding is still impractical, but it's the correct interpretation ("as wide as possible") and matches the spirit of the C standard's treatment of negative widths.

## Test StatusNew test added in `tests/test_strings.c`:

```
  [OK] %*d INT_MIN width: digits correct
  [OK] %*d INT_MIN width: return value positive (no overflow)
  l_snprintf tests: PASSED

Full ./Taskfile test suite passes.

Generated by 🌈 Repo Assist at {run-started}. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@1f672aef974f4246124860fc532f82fe8a93a57e

When a negative value is passed as a dynamic width via %*d (and
friends), the spec says to treat it as a left-justify flag and a
positive width. The previous code did:

    width = -width;

If the caller passes INT_MIN, negating it is undefined behaviour in
C (signed integer overflow). On x86 the result wraps back to
INT_MIN, which is still negative, and the subsequent pad calculation
overflows too — making the return value wrap to a large negative
number.

Fix: clamp INT_MIN to INT_MAX before negation, so the output is
well-defined (content printed left-justified, return value ==
INT_MAX). Add a test that exercises this path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants