I’ve been able to find some time, lately, to work on my project backlog. And because it’s 2026, I’ve been using AI as a diligent intern.

I’ve ranted before about seccomp, but still used it for a project or two. But then, rarely, it triggered an unexpected openat. That’s exactly the kind of I do want to detect and kill the binary for, so I don’t just want to allow it. I want to know where it’s coming from.

strace showed it’s trying to read /proc/sys/vm/overcommit_memory.

It’s certainly not my code. But just the Rust transitive dependency tree is quite a few crates:

$ cargo tree | sed -r 's/^[^a-z]+//;s/ .*//' | sort -u | wc -l
236

Step 1 was to run it in gdb, and reproduce the problem. But it’s a bit trickier than that, because seccomp fully kills the process, so no backtrace. And setting breakpoints requires a few more syscalls to work, just for the process to work under gdb (e.g. sigaltstack).

And turns out some calls fail with EINTR if running under a debugger.

Yes, I can fix all these things. But why not put the AI intern on it?

AI coding detour

I’ve previously vibe coded a raytracer just with the prompt “make a raytracer for the files in this directory”. The files in question were my POV-Ray files converted from old Quake demos.

It worked very well that time (flat shaded and textured), so why not?

(youtube’s compression is Very Aggressive™. The originals look crisp)

Could the AI solve it?

Skipping to the end, yeah:

openat(AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY|O_CLOEXEC)
    = -1 EACCES (Permission denied)

This is now an error instead of killing the binary.

Could I have solved it?

Sure. But it reminds me of the well publicised Google interview question “how many piano tuners are there in New York city?”.

The best answer I’ve heard is “is it important for the company? — Yes — Then I’d look it up”. That’s the right answer, of course. There’s another “more right” answer to the “why are manhole covers round?” question.

Is it important to know which of my transitive dependencies try to read this file? Not really. I could blanket allow that file to be read. I know what that file is, and I actively don’t care if an attacker can read it.

So without AI I would not have spent more than 30 seconds trying to find out. And I spent a couple of seconds doing rg overcommit_ ~/.cargo, only showing references in jemalloc, which this project doesn’t transitively depend on.

So it’s likely somewhere in some C code. Or in the Rust standard library.

So instead I spent 9 minutes reading up on the QUIC protocol for a couple of my other projects, while the AI went brrr.

The AI session

Prompt

Sometimes the code tries to read /proc/sys/vm/overcommit_memory readonly and crashes because seccomp. Why does this happen?

Output

[… thinking out loud and searching the web before the final answer …]

• It’s glibc malloc, not our code.

  glibc’s Linux malloc has a lazy check_may_shrink_heap() path. When it
  considers shrinking a malloc heap, it checks whether Linux is in strict
  overcommit mode by opening:

  /proc/sys/vm/overcommit_memory

  readonly with O_RDONLY | O_CLOEXEC, reading one byte, then caching the
  result.  Source: glibc malloc-sysdep.h
  (https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/malloc-sysdep.h.html#34),
  called from heap shrink logic in arena.c
  (https://codebrowser.dev/glibc/glibc/malloc/arena.c.html#506).

  It is “sometimes” because that path is lazy and allocation-pattern dependent.
  With multiple worker threads, glibc creates per-thread malloc arenas; later
  frees/trims can hit heap_trim -> shrink_heap -> check_may_shrink_heap. If the
  first time that happens is after seccomp is loaded, our default KillProcess
  action kills the process on openat.

  Best fix is probably not to broadly allow file reads. Add a targeted seccomp
  rule that returns EACCES/EPERM for openat instead of killing, ideally only for
  readonly/CLOEXEC flags. glibc treats open failure as “can’t check, assume no
  strict shrink requirement” and caches that, while Landlock still prevents
  actual filesystem access. Warming this before seccomp is possible but brittle
  because the triggering path is allocator-version and arena-state dependent.

─ Worked for 3m 42s ──────────────────────────────────────────────────────────────

Prompt

Sounds good. Change it to do that.

Output

⚠ Heads up, you have less than 25% of your 5h limit left. Run /status for a breakdown.

[uh oh. But to be clear this was far from my first task of the 5h window. In
 fact I have another unrelated task running concurrently]

• Changed src/privs.rs:120 so readonly openat returns EACCES instead of killing
  the process under seccomp.

  The rule is conditional on openat argument 2, masked with O_ACCMODE, being
  O_RDONLY. That covers glibc’s lazy /proc/sys/vm/overcommit_memory probe while
  still leaving write-capable opens under the default KillProcess action. No
  read syscall was added.

  Verified with:

  - cargo check --bins
  - cargo test --bin tarweb-h3
  - cargo build --bin tarweb-h3
  - Live tarweb-h3 --threads 2 + curl -sk --http3 https://[::1]:18083/README.md success
    ful.

─ Worked for 5m 39s ────────────────────────────────────────────────────────────────── 

### Final patch

Yeah. Not bad. I can work with this.

diff –git a/src/privs.rs b/src/privs.rs index 19b6307..cce5ef6 100644 — a/src/privs.rs +++ b/src/privs.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result, anyhow}; use caps::CapSet; -use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall}; +use libseccomp::{ScmpAction, ScmpArgCompare, ScmpCompareOp, ScmpFilterContext, ScmpSys call}; use tracing::{info, trace, warn};

/// Drop privileges to bare minimum. @@ -127,18 +127,32 @@ fn drop_caps() -> Result<()> { ] { f.add_rule(ScmpAction::Allow, ScmpSyscall::from_name(name)?)?; }

  • // glibc malloc may lazily probe /proc/sys/vm/overcommit_memory.
  • f.add_rule_conditional(
  • ScmpAction::Errno(libc::EACCES),
  • ScmpSyscall::from_name(“openat”)?,
  • &[ScmpArgCompare::new(
  • 2,
  • ScmpCompareOp::MaskedEqual(libc::O_ACCMODE as u64),
  • libc::O_RDONLY as u64,
  • )],
  • )?;

    if with_rustls { // Rustls does some memory allocation.

Follow-up

So, now it works. But I’d want to adjust it a bit, to not merely return EACCES for any probing of files such as /etc/passwd or TLS certificates. I want that to terminate the binary.

For merely denying filesystem access on a per path prefix basis I use landlock, but that doesn’t kill the binary.

I’m not aware of a way for seccomp to block specific paths (again, seccomp sucks), but maybe the AI knows? But oh no:

╭─────────────────────────────────────────────────────────────────────────────────╮
     […]
│  5h limit:             [░░░░░░░░░░░░░░░░░░░░] 0% left (resets 19:26)            │
│  Weekly limit:         [██████████░░░░░░░░░░] 48% left (resets 16:12 on 18 Jun) │
╰─────────────────────────────────────────────────────────────────────────────────╯

So maybe it’s time to go outside in the nice weather? “Touch grass”, as the kids say.