google/benchmark

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

Open

#2184 opened on Apr 29, 2026

View on GitHub
 (4 comments) (0 reactions) (0 assignees)C++ (7,968 stars) (1,539 forks)batch import
bughelp wanted

Description

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.

Contributor guide