Skip to content
zviz

Understanding the selective-denial policy

What "132 allow / 24 deny / 1 filter" means, why ZViz denies instead of emulating, and how the socket filter works.

Level: intermediateTime: 10 min

ZViz’s policy is small enough to hold in your head: 132 syscalls allowed, 24 denied, 1 argument-filtered. This guide explains what that means and why it is shaped that way.

Deny, don’t emulate

A userspace kernel like gVisor’s Sentry reimplements the syscall ABI so the host kernel runs fewer syscalls. That buys compatibility at the cost of an emulation layer every syscall passes through.

ZViz takes the other road. Allowed syscalls run directly on the host kernel — no translation, no per-syscall tax. The dangerous ones are simply refused at the seccomp-BPF boundary and return EPERM before the kernel ever sees them.

The three outcomes

Every syscall a workload makes is classified into one of three buckets:

  • ALLOW (132) — reaches the host kernel and runs at native speed.
  • DENY (24) — returns EPERM at the BPF filter. The dangerous set: ptrace, mount, unshare, init_module, kexec_load, and the rest.
  • FILTER (1)socket() is inspected inline so specific families can be permitted or refused.

Why filter socket()?

A blanket socket() denial would break too much legitimate networking, but allowing every family would let a workload open raw AF_PACKET sockets. Argument-filtering threads the needle: permit the safe families, refuse the dangerous ones. It is the one place ZViz needs more than a yes/no answer.

The trade-off, stated plainly

Denial means some workloads will not run under ZViz — anything that needs a denied syscall inside the container. That is the explicit cost of a smaller surface. When you hit it, gVisor is the tool that emulates the missing syscalls. See how it works for the full syscall path.

Frequently Asked Questions

Why deny syscalls instead of emulating them?

Emulation (as in gVisor) buys compatibility but adds a userspace-kernel layer every syscall passes through. Denial keeps allowed syscalls on the host kernel at native speed and refuses the dangerous ones outright. Different trade-off — smaller surface, native speed, less compatibility.

Why is socket argument-filtered instead of denied?

A blanket socket() denial would break too many legitimate workloads. Argument-filtering lets a policy permit safe socket families while refusing dangerous ones like AF_PACKET, giving finer control than allow-or-deny.

Related

Run untrusted code with a smaller attack surface

ZViz is open source under Apache 2.0. Build it and point it at any OCI bundle.