Skip to content
/linux-syscalls

Process & Thread · 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