Soft & Hard Limits Who Can Change What, Inheritance Rules, Per-User-ID Limits

 

36.2b — Soft & Hard Limits
Who Can Change What, Inheritance Rules, Per-User-ID Limits | EmbeddedPathashala

The Soft/Hard Limit System

Every resource has two limits that work together. The soft limit is what the kernel actually enforces — it is the wall a process hits when consuming the resource. The hard limit is a governance ceiling on how high the soft limit can be raised. Think of the soft limit as the current speed limit and the hard limit as the absolute maximum speed the road is ever allowed to have.

Who Can Change What

Process Type Can change rlim_cur (soft) Can change rlim_max (hard)
Unprivileged process ✓ Can set to any value from 0 up to rlim_max ✗ Can only lower it (irreversibly). Cannot raise.
Privileged (CAP_SYS_RESOURCE) ✓ Can set to any value including above rlim_max ✓ Can raise or lower as long as rlim_max ≥ rlim_cur
Irreversibility of hard limit lowering: When an unprivileged process lowers its hard limit, it cannot raise it again. This is intentional — once you surrender a privilege ceiling, it is gone. This is why systems administrators think carefully before reducing hard limits in startup scripts.

Inheritance — fork() and exec()

Resource limits are inherited by child processes created with fork(), and they are preserved across exec(). This is how the shell’s ulimit settings cascade to all programs the user runs.

fork() inheritance:
The child process starts with an exact copy of the parent’s resource limits. If parent had RLIMIT_NOFILE = 1024/4096 (soft/hard), the child starts with the same values.
exec() preservation:
When a process calls exec() to replace itself with a new program, the resource limits are preserved. The new program starts with the same limits the old program had.
/* Common pattern: sandbox an untrusted program */
pid_t pid = fork();
if (pid == 0) {
    struct rlimit rl;
    /* Set tight limits on the child BEFORE exec */
    rl.rlim_cur = rl.rlim_max = 30;          /* max 30 procs  */
    setrlimit(RLIMIT_NPROC, &rl);
    rl.rlim_cur = rl.rlim_max = 64*1024*1024; /* 64 MB memory */
    setrlimit(RLIMIT_AS, &rl);
    rl.rlim_cur = rl.rlim_max = 10;           /* 10 sec CPU   */
    setrlimit(RLIMIT_CPU, &rl);
    /* exec() inherits these limits — untrusted code is caged */
    execvp(argv[1], &argv[1]);
    perror("execvp"); exit(1);
}

Per-Process vs Per-User-ID Measurement

Most resource limits are measured against the process itself only. But some limits — notably RLIMIT_NPROC and RLIMIT_SIGPENDING — are measured against all processes with the same real user ID.

The reason for the user-ID scope becomes clear with RLIMIT_NPROC. If the limit only applied to the process’s own children, a process could trivially bypass it by having each child spawn more children. So instead, the limit counts all processes owned by that user ID — making it a genuine ceiling on total process creation by a user.

Important caveat: Even with per-user-ID measurement, the limit is only checked in processes where it has been set (i.e. processes that have called setrlimit() or inherited the setting). Another process owned by the same user that never set the limit (meaning it has RLIM_INFINITY) is not constrained by what other processes set.

Table 36-1 — All Resource Constants

Resource Constant What It Limits SUSv3 Scope
RLIMIT_AS Virtual address space (bytes) process
RLIMIT_CORE Core dump file size (bytes) process
RLIMIT_CPU CPU time (seconds) process
RLIMIT_DATA Data segment size (bytes) process
RLIMIT_FSIZE File size (bytes) process
RLIMIT_MEMLOCK Locked memory (bytes) BSD process
RLIMIT_MSGQUEUE POSIX MQ bytes (Linux 2.6.8+) user ID
RLIMIT_NICE Nice value ceiling (Linux 2.6.12+) process
RLIMIT_NOFILE Max file descriptor + 1 process
RLIMIT_NPROC Process count per real user ID BSD user ID
RLIMIT_RSS RSS pages (not enforced on Linux) BSD
RLIMIT_RTPRIO RT priority ceiling (Linux 2.6.12+) process
RLIMIT_RTTIME RT CPU time µs (Linux 2.6.25+) process
RLIMIT_SIGPENDING Queued signals (Linux 2.6.8+) user ID
RLIMIT_STACK Stack segment size (bytes) process

Code Example — Demonstrating Limit Rules

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/resource.h>

int main(void)
{
    struct rlimit rl;
    getrlimit(RLIMIT_STACK, &rl);

    printf("Stack soft: %lld bytes\n", (long long)rl.rlim_cur);
    printf("Stack hard: %lld bytes\n", (long long)rl.rlim_max);

    /* Unprivileged: try to LOWER the hard limit */
    rlim_t original_hard = rl.rlim_max;
    rl.rlim_max = rl.rlim_cur;   /* lower hard to equal soft */
    if (setrlimit(RLIMIT_STACK, &rl) == 0) {
        printf("Successfully lowered hard limit to %lld\n",
               (long long)rl.rlim_max);
    }

    /* Now try to RAISE the hard limit back — this will fail */
    rl.rlim_max = original_hard;
    if (setrlimit(RLIMIT_STACK, &rl) == -1) {
        if (errno == EPERM)
            printf("EPERM: Cannot raise hard limit — irreversible!\n");
    }

    /* Unprivileged: try to raise soft ABOVE hard — should fail */
    getrlimit(RLIMIT_STACK, &rl);
    rl.rlim_cur = rl.rlim_max + 1;
    if (setrlimit(RLIMIT_STACK, &rl) == -1) {
        if (errno == EINVAL)
            printf("EINVAL: Cannot set soft above hard\n");
    }

    return 0;
}
/* Compile: gcc -o limits_rules limits_rules.c */

Interview Questions

Q1. Can an unprivileged process raise its hard limit? What about lower it?

An unprivileged process can only lower its hard limit, and this lowering is irreversible — it cannot be raised again without root/CAP_SYS_RESOURCE privilege. It cannot raise the hard limit at all. Only a privileged process (CAP_SYS_RESOURCE) can raise the hard limit.

Q2. How are resource limits inherited in Linux?

Resource limits are inherited by child processes created via fork() — the child gets exact copies of the parent’s limits. They are also preserved across exec() — when a process replaces itself with a new program, the limits remain unchanged. This is how shell ulimit settings cascade to all commands the shell runs.

Q3. Name two resource limits that are measured against all processes with the same real user ID, not just the process itself.

RLIMIT_NPROC (total processes for that user ID) and RLIMIT_SIGPENDING (queued signals for that user ID). These are per-user-ID because a per-process scope would be trivially bypassable.

Q4. Are resource limits enforced for privileged processes?

In most cases yes — resource limits apply to both privileged and unprivileged processes. The exception is RLIMIT_NPROC: this is not enforced for processes with CAP_SYS_ADMIN or CAP_SYS_RESOURCE. Most other limits still apply even to root.

Leave a Reply

Your email address will not be published. Required fields are marked *