google/benchmark

[BUG] MaybeReenterWithoutASLR() infinite loop under AppArmor (not covered by #1984 fix)

Open

#2184 aperta il 29 apr 2026

Vedi su GitHub
 (4 commenti) (0 reazioni) (0 assegnatari)C++ (1539 fork)batch import
bughelp wanted

Metriche repository

Star
 (7968 star)
Metriche merge PR
 (Merge medio 1g 23h) (21 PR mergiate in 30 g)

Descrizione

Description

MaybeReenterWithoutASLR() causes an infinite execv() loop when running google-benchmark binaries inside VSCode's integrated terminal on a Linux machine with AppArmor enabled. This is a different root cause from #1984 (which was fixed in #1985/v1.9.4).

Environment

  • google-benchmark v1.9.5
  • Linux (Ubuntu), kernel with AppArmor enabled (lockdown,capability,landlock,yama,apparmor,ima,evm)
  • Triggered specifically inside VSCode integrated terminal (Electron child process tree). Works fine in gnome-terminal on the same machine.

Root Cause Analysis

The fix from #1985 checks the personality before the execv():

const auto new_personality = personality(0xffffffff);
if ((internal::get_as_unsigned(new_personality) & ADDR_NO_RANDOMIZE) == 0)
    return;  // guard: flag didn't stick
execv(argv[0], argv);

In my case, this guard does NOT trigger because personality() does report ADDR_NO_RANDOMIZE as set within the current process. However, execv() causes the kernel/AppArmor to reset the personality in the new process image. The newly exec'd process then sees ASLR is still active, tries to disable it again, succeeds "locally", exec's again — infinite loop.

Evidence from GDB (addresses change on each exec = ASLR still active):

process 452521 is executing new program: /tmp/mini_bench_debug
Breakpoint 1, main (argc=2, argv=0x7ffc55608698)
process 452521 is executing new program: /tmp/mini_bench_debug
Breakpoint 1, main (argc=2, argv=0x7fff874b84f8)
process 452521 is executing new program: /tmp/mini_bench_debug
Breakpoint 1, main (argc=2, argv=0x7fff7f778408)
... (loops forever at 100% CPU)

Additional evidence:

VIRT=12KB in top (binary never fully loads due to constant re-exec)
cat /proc/$PID/maps shows only [stack] and [vsyscall]
cat /proc/$PID/syscall reports running (no syscall, pure userspace loop)
cat /sys/kernel/security/lsm → lockdown,capability,landlock,yama,apparmor,ima,evm
No LD_PRELOAD, no kernel module interference, no seccomp

Difference from #1984

Aspect #1984 (Docker) This bug (AppArmor)
personality() before exec Reports flag NOT set Reports flag IS set (yes)
Guard triggers? Yes (fix works) No (fix doesn't help)
execv() behavior N/A (doesn't reach exec) Resets personality on new image
Result Fixed by pre-exec check Still loops infinitely

Possible Fix

The only reliable approach is to check after exec whether the personality was actually inherited. One way:

void MaybeReenterWithoutASLR(int /*argc*/, char** argv) {
  if (!argv) return;

#ifdef BENCHMARK_OS_LINUX
  const auto curr_personality = personality(0xffffffff);
  if (curr_personality == -1) return;
  if (internal::get_as_unsigned(curr_personality) & ADDR_NO_RANDOMIZE) return;

  const auto proposed_personality =
      internal::get_as_unsigned(curr_personality) | ADDR_NO_RANDOMIZE;
  const auto prev_personality = personality(proposed_personality);
  if (prev_personality == -1) return;

  // Verify the flag was actually applied (fix for #1984/docker).
  const auto new_personality = personality(0xffffffff);
  if ((internal::get_as_unsigned(new_personality) & ADDR_NO_RANDOMIZE) == 0)
    return;

  // Check if we already attempted re-exec (environment sentinel).
  // This catches the case where personality() succeeds but execv()
  // resets it (e.g. AppArmor restricting the child process tree).
  if (getenv("BENCHMARK_ASLR_NO_REEXEC")) return;
  setenv("BENCHMARK_ASLR_NO_REEXEC", "1", 1);

  execv(argv[0], argv);
#endif
}

Alternatively, a simpler CMake option like BENCHMARK_ENABLE_ASLR_REEXEC (defaulting to ON) would allow users to opt out of this behavior entirely.

Workaround

Comment out or patch the execv() call in src/benchmark.cc:859.

Reproducibility

Minimal repro: compile any BENCHMARK_MAIN() binary and run it inside VSCode's integrated terminal on a machine with AppArmor. The binary will spin at 100% CPU and never produce output.

Guida contributor