Safeguard
Application Security•

CWE-243 chroot Jail Without Changing Working Directory

Calling chroot() without a following chdir() leaves the working directory outside the jail, so relative paths can still reach the real filesystem.

Safeguard Security Team
3 min read

CWE-243 applies to C and C++ on Unix. The product uses the chroot() system call to create a jail but does not change the working directory afterward. That does not prevent access to files outside the jail.

How it happens

chroot() changes what the process sees as the root directory. It does not change the process's current working directory. If the working directory was outside the new root, relative paths can still point at resources outside the jail.

Vulnerable pattern:

/* Vulnerable: working directory is unchanged */
if (chroot("/var/jail") != 0) {
    perror("chroot");
    exit(1);
}
/* A relative path here may still resolve outside the jail */
FILE *f = fopen("../../etc/passwd", "r");

Fixed pattern:

if (chroot("/var/jail") != 0) {
    perror("chroot");
    exit(1);
}
if (chdir("/") != 0) {      /* move inside the jail */
    perror("chdir");
    exit(1);
}

Note that the fix checks the return value of both calls. Ignoring a failed chroot() or chdir() would bring back the problem under a different name.

What an attacker gains

The record lists a confidentiality impact: the ability to read files or directories outside the jail. An attacker who controls a path the jailed process opens may reach files the jail was meant to hide.

How to find it

MITRE lists automated static analysis. You can also search the source for chroot( and confirm a chdir to the new root follows immediately, with both return values checked.

How to fix it

The record has no mitigation list. The remedy follows from the description: change the working directory to the root of the jail right after calling chroot(), and stop on any failure.

Treat chroot as one layer. It was not built as a complete sandbox, so pair it with dropped privileges and other containment where the process handles untrusted data.

Where this fits in a wider design

A jail that leaks through the working directory is a good reminder that isolation features have fine print. chroot() changes one thing, the root directory the process sees. It does not change the current directory, it does not drop privileges, and it does not limit what the process can do with the resources it already has open.

Because of this, treat the two calls as a pair. Change the root, then change the working directory to the new root, and check both. Review code that uses chroot() with that pair in mind, and ask what else the jailed process can still reach.

Static analysis is the detection method MITRE lists, and it suits this pattern because the rule is local: a chroot call with no chdir after it. A code search for chroot( takes a few seconds and may be worth running on any C or C++ codebase that confines processes.

Quick check

Why is chdir needed after chroot?

chroot() does not move the current working directory. Without a chdir to the new root, relative paths can still refer to files outside the jail.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.