Skip to content
/linux-syscalls

Prozesse & Threads · Section 2

execve(2)

Replace the current process image with a new program.

Signature

#include <unistd.h>

int execve(const char * pathname, char *const [] argv, char *const [] envp);
pathname
Path to the executable. Absolute or relative; must be a regular file with execute permission or an interpreter script (#!/…) the kernel can load.
argv
NULL-terminated array of argument strings. By convention argv[0] is the program name as the caller wishes it to appear.
envp
NULL-terminated array of environment strings of the form NAME=value. Pass environ to inherit, or a curated array to scrub sensitive variables.

Description

execve() replaces the calling process's text, data, BSS, and stack segments with those of a new program, while preserving the process ID, parent process ID, open file descriptors (unless O_CLOEXEC is set), pending signals, and resource limits. The new program is given argv as its argument vector and envp as its environment; both arrays must be NULL-terminated. On success, execve() does not return — control transfers to the entry point of the new program. On failure it returns -1 with errno set and the caller continues. Set-user-ID and set-group-ID bits on the new binary may take effect subject to no-new-privs, NSU mounts, and capability rules.

Architecture mapping

ArchitectureNumberABIEntry point
x86 (i386)11i386sys_execve
x64 (x86_64)5964sys_execve
ARM64 (aarch64)221——

Kernel history

Introduced in Linux 1.0.

  1. 1.0

    execve() has been part of Linux since 1.0 and largely follows POSIX.1, with Linux-specific behaviour around #! interpreter handling and the ARG_MAX accounting.

  2. 3.19

    execveat() was added as a directory-fd-relative variant (analogous to openat) and to support executing file descriptors directly (AT_EMPTY_PATH).

  3. 5.7

    The maximum depth of #! interpreter recursion was hardened against pathological cases that could allow kernel-stack exhaustion via deeply nested interpreter chains.

seccomp & containers

Docker default profile

Allowed

Podman default profile

Allowed

execve() and execveat() are on Docker / Podman default allow-lists. Blocking execve() is the single most effective container hardening for workloads that should never spawn binaries — e.g. a web server that only reads files and writes to sockets. After all required binaries have been started, an init wrapper can install a seccomp filter that denies execve() / execveat(), removing an entire class of post-exploitation moves (shell drops, lateral binary loading). no_new_privs and CAP_SYS_PTRACE removal compose nicely with this.

libseccomp

// Block execve from a process — disables ability to spawn binaries
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execve), 0);
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execveat), 0);

strace example

$ strace -f -e execve /bin/sh -c 'ls /tmp'
execve("/bin/sh", ["/bin/sh", "-c", "ls /tmp"], 0x7ffd8c4f3a18 /* 28 vars */) = 0
execve("/usr/bin/ls", ["ls", "/tmp"], 0x55a9b2e1d010 /* 28 vars */) = 0
+++ exited with 0 +++

strace -f follows fork/clone children, so execve from sub-shells is captured. The third argument (envp) is shown as 0x… /* N vars */ by default — pass --strings-in-hex=none -v to expand. Use -e trace=execve,execveat to filter.

Security & observability

execve() is the canonical post-exploitation primitive — every shell drop, every binary loader, every privilege-escalation path passes through it. The audit subsystem fires execve records (-a always,exit -F arch=b64 -S execve), and the eBPF tracepoint sched_process_exec captures the program name, parent, and environment. Rootkits sometimes hook execve to substitute /bin/ps with a filtered version, or to spawn a hidden binary on certain inputs; comparing tracepoint output against /proc/<pid>/exe is the standard cross-check. For container security, an unexpected execve from a workload pod is almost always worth paging on.

Errors

E2BIG
The combined argv + envp + auxiliary vector exceeds the kernel's ARG_MAX limit (typically 128 KiB on Linux, or 1/4 of RLIMIT_STACK on newer kernels).
EACCES
Search permission denied on a path component, or pathname is not executable, or the filesystem is mounted noexec.
EFAULT
—
EINVAL
—
EIO
—
EISDIR
—
ELIBBAD
An ELF interpreter (e.g. ld.so) named in the binary could not be loaded.
ELOOP
Too many symbolic-link or interpreter levels were traversed.
EMFILE
—
ENAMETOOLONG
—
ENFILE
—
ENOENT
—
ENOEXEC
The file is recognised but is not in an executable format the kernel can run (e.g. wrong architecture, broken ELF).
ENOMEM
—
ENOTDIR
—
EPERM
The filesystem is mounted nosuid and the binary is set-UID/GID, or no_new_privs blocks privilege elevation.
ETXTBSY
The binary is currently open for writing by another process; execution would race with that writer.

Related syscalls