Skip to content

Fix program_invocation_short_name when argv[0] has no '/'#3972

Open
User-green wants to merge 1 commit into
ptitSeb:mainfrom
User-green:fix-progname-short-name-null
Open

Fix program_invocation_short_name when argv[0] has no '/'#3972
User-green wants to merge 1 commit into
ptitSeb:mainfrom
User-green:fix-progname-short-name-null

Conversation

@User-green

@User-green User-green commented Jun 19, 2026

Copy link
Copy Markdown

WARNING!

The whole thing was done by claude, I only tested! It works in my given case and might be usefull - but it also might be harmfull on some deep level I dont understand.

I went ahead and created this PR because it would be better than just opening an issue while sitting on a fix.

Here is what Claude gave to me:

The bug in one sentence

box64 computes the emulated program_invocation_short_name as
strrchr(argv[0], '/') + 1, which is NULL + 1 == (char*)1 (a wild pointer)
whenever argv[0] contains no /.

Where

src/wrapped/wrappedlibc.c, in the libc CUSTOM_INIT macro (~line 4928):

my___progname_full = my_program_invocation_name = box64->argv[0];
my___progname = my_program_invocation_short_name =
    strrchr(box64->argv[0], '/') + 1;          // <-- NULL + 1 when no '/'

my_program_invocation_name (full) is fine; only the short name is wrong.

Why it's wrong

glibc's program_invocation_short_name is the basename of argv[0]: the part
after the last /, or the whole string when there is no /. The box64 code
omits the "no slash" case, so it returns (char*)1 instead of argv[0].

Impact

Any emulated program that reads program_invocation_short_name or __progname
and uses it as a string (logging, error prefixes, std::string, etc.)
dereferences (char*)1 and crashes — but only when argv[0] has no /.

When argv[0] has no / in practice

  • A process that renames itself / is exec'd with a bare name (e.g. "adb").
  • This is why it shows up as a forked-child crash: the parent has a full path
    argv[0] (has /, works), the renamed child has a bare argv[0] (crashes).
    It is not actually fork-specific and not dynarec-specific.

Reproducer (deterministic)

x86_64 Android adb under box64:

adb kill-server; adb start-server

adb spawns a fork-server child renamed to adb. liblog's
__android_log_set_default_tag builds a std::string from
program_invocation_short_name -> strlen((char*)1) -> SIGSEGV. adb version
(no fork/rename) is fine. BOX64_DYNAREC=0 crashes identically (rules out
dynarec).

Evidence (how it was confirmed)

Interpreter-mode build under gdb on the box64 core:

  • Faulting native call: strlen with first arg RDI = 0x1.
  • Guest call chain: __cxa_guard_release -> __android_log_set_default_tag ->
    std::__1::basic_string::basic_string(char const*) -> the wrapped strlen.
  • In box64's own globals: my_program_invocation_short_name == 0x1, while
    my_program_invocation_name points to a valid "adb".

The fix

With no /, use argv[0] itself (matches glibc):

my___progname = my_program_invocation_short_name =
    (strrchr(box64->argv[0], '/') ? strrchr(box64->argv[0], '/') + 1
                                  : box64->argv[0]);

(Calling strrchr twice is fine; this runs once at init. A temp variable would
avoid the double call if preferred.)

Verification done

Built current main (commit c018889) from source as v0.4.3 on aarch64
(Cortex-A55, Arch Linux ARM, kernel 6.18), with this one-line change.

  • Before: adb start-server SIGSEGVs in the forked child (dynarec on AND off).
  • After: adb start-server -> "daemon started successfully", server answers
    adb version (dynarec on AND off).

Related (worth mentioning, out of scope for the 1-line PR)

The 32-bit path src/wrapped32/wrappedlibc.c has a different but adjacent
mistake: strrchr(box64->argv[0], '/') with no + 1 — that keeps the
leading / when present, and is NULL when absent. Not reproduced here; left out to keep the PR minimal. Could be fixed the same way.

Closes #3971.

my_program_invocation_short_name (and my___progname) were computed as
strrchr(box64->argv[0], '/') + 1. When argv[0] contains no '/', strrchr
returns NULL, and NULL + 1 yields (char*)1 — an invalid pointer. Guest
code that reads program_invocation_short_name / __progname then
dereferences it and crashes (e.g. a std::string ctor doing strlen((char*)1)).

This triggers when a process is exec'd with a bare argv[0] (no path), such
as a renamed fork-server child, and shows up as a SIGSEGV in the forked
child that does not reproduce when argv[0] is a path.

Match glibc: with no '/', the short name is the whole argv[0].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@User-green User-green marked this pull request as ready for review June 19, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADB crash under emulation

1 participant