Decoded: chroot (coreutils)

[Back to Project Main Page]

Note: This page explores the design of command-line utilities. It is not a user guide.
[GNU Manual] [No POSIX requirement] [Linux man] [FreeBSD man]

Logical flow of chroot command (coreutils)

Summary

chroot - run a command with a different root directory

[Source] [Code Walkthrough]

Lines of code: 431
Principal syscall: chroot() (Linux)
Support syscalls: execvp(), chdir()
Options: 5 (0 short, 5 long)

GNU implementation is possibly the earliest utility -- chroot syscall appeared in Version 7 UNIX (1979)
Added to Shellutils in March 1996 [First version]
Number of revisions: 110 [Code Evolution]

There is no current POSIX interface for chroot, which complicates utility execution across platforms. Here is the legacy POSIX interface

Helpers:
  • is_root() - Tests if an input path is the same as '/'
  • gid_set() - Tests if gid has a nontrivial value
  • gid_unset() - Tests if gid is -1
  • parse_additional_groups() - Parses comma separated groups in to a gid array
  • set_groups() - Simulates setgroups() syscall with success/fail conditions
  • uid_set() - Tests if uid has a nontrivial value
  • uid_unset() - Tests if uid is -1
External non-standard helpers:
  • die() - Exit with mandatory non-zero error and message to stderr
  • error() - Outputs error message to standard error with possible process termination
  • ignore_value() - Prevents warning from GCC warn_unused_result attribute
  • parse_user_spec() - Breaks a user:group in to components

Setup

The bulk of chroot executes within main(). The function begins by initializing nine locals:

  • c - The current command line option letter we're parsing
  • gid - The group ID parsed from userspec
  • groups - Group list input from the --groups option
  • n_gids - The number of gid entries
  • out_gids - The resulting gids parsed from groups
  • skip_chdir - Flag to skip changing working directory
  • uid - user ID parsed from userspec
  • username - The user name pulled from the user file
  • userspec - The user:group input from the --userspec option

Parsing

Parsing collects options and a target path which considers the following questions:

  • Should the new process use a specific group?
  • Should the new process run under different credentials?
  • Should the working directory change?

Parsing failures

These failure cases are explicitly checked:

  • No new path to change root
  • Trying to skip changing directory if the newroot isn't root

User specified parsing failures result in a short error message followed by the usage instructions. Access related parsing errors die with an error message.


Execution

chroot execution follows a fairly linear process:

  • Set input users and groups as specified
  • Perform chroot() to the new location
  • Change working directory to the new root
  • Update all users and groups to the new settings (with supplemental groups)
  • Execute the desired command in the new root environment (default to new shell)

Since execvp() doesn't return any further execution in chroot implies a failure

Failure cases:

  • Can't change root (chroot() fails)
  • Can't change working directory (chdir() fails)
  • Unknown group provided
  • Can't change group ID
  • Can't change user ID
  • execvp failed for any reason

Failures output an error message to STDERR and return without displaying usage help


[Back to Project Main Page]