Files
shadow/docs/man/chfn.1.md
T
Pierre Warnier b6c318d1d5 chfn/chsh: interactive mode, and fix a setuid path oracle (#268)
* shadow-core: add a shared terminal helper

Reading a line and reading a password were open-coded in pam.rs, and
every prompt outside a PAM conversation had no helper at all.

tty.rs holds all of it: prompt_line, read_password and read_stdin_line,
with the EchoGuard that restores the terminal. Ctrl-C at a password
prompt used to leave echo disabled -- the signal terminates the process
without unwinding, so the guard's destructor never ran. The read now
blocks SIGINT, SIGQUIT and SIGTSTP for its duration, which is what
readpassphrase(3) does, and restores the mask on the way out.

The prompt prefers /dev/tty and falls back to stderr and stdin when
there is no controlling terminal (ENXIO/ENODEV), the case that broke
password changes under su -c.

* chfn: prompt for fields, and check values before asking for a password

chfn(1) with no field option prompts for each field with its current
value; ours refused with 'no flags specified'. It now prompts, and an
empty answer keeps the field.

Three fixes come with it:

  - CHFN_RESTRICT is enforced on the values, not on which options were
    passed. Interactive input went through unrestricted, and a field the
    caller may not change is now not prompted for at all.
  - Values are validated before the PAM conversation, so a rejected
    value no longer costs the caller a password prompt.
  - mutate_passwd no longer calls setuid(0). euid 0 is all the lock and
    the atomic write need; raising the real uid made caller_is_root(),
    which is deliberately real-uid based, answer true for everyone.

The five sub-fields are now one Changes value with the collect,
validate, permission and apply steps on it, which is what let uumain
shrink rather than grow.

* chsh: prompt for the shell, and stop leaking path existence

Three changes to the self-service path.

chsh(1) with no -s prompts for the new shell with the current one in
brackets; ours refused with 'no shell specified'. Pressing ENTER, or
repeating the current value, is now a successful no-op.

An empty shell is accepted. passwd(5) gives the field no meaning of its
own and login falls back to /bin/sh, so -s '' selects the system default
rather than naming a program that does not exist.

The order of the two checks on a shell now matters and is documented.
chsh is setuid-root, so Path::exists answers with root's view of the
filesystem: testing existence first made the tool an oracle for paths
the caller cannot stat, one probe at a time --

    chsh -s /root/.ssh/id_ed25519

answering 'is not listed in /etc/shells' for a file that exists and
'does not exist' for one that does not. For a non-root caller the
membership test now runs first and discloses nothing, /etc/shells being
world-readable. Root still bypasses the list but not existence.

Validation also moved ahead of the PAM conversation, so a refused shell
no longer costs a password prompt, and mutate_passwd no longer calls
setuid(0): euid 0 is enough, and raising the real uid made
caller_is_root() answer true for every caller.

* docs: rewrite the chfn and chsh man pages; cover self-service end to end

The man pages documented neither interactive mode, nor CHFN_RESTRICT,
nor the restricted-account rule, nor what an empty shell field means,
and chfn's still said a field option was mandatory. Both now carry
permissions, value restrictions, configuration, files and worked
examples, and chsh explains why the two shell checks run in the order
they do.

The deployment suite gains eighteen assertions against the real setuid
binaries: interactive chfn and chsh through PAM, an empty answer as a
no-op, CHFN_RESTRICT enforced on interactive input, the empty shell
field, and the oracle itself -- an existing and a missing path under
/root must give a non-root caller the same answer, with no password
prompt before it.
2026-09-04 13:53:30 +02:00

4.5 KiB

chfn(1) - change user finger information

NAME

chfn - change real user name and information

SYNOPSIS

chfn [options] [LOGIN]

DESCRIPTION

The chfn command changes a user's finger information: the full name, office room number and phone numbers that finger(1) and similar tools display. These are stored as comma-separated sub-fields of the GECOS field of /etc/passwd (see passwd(5)):

Full Name,Room Number,Work Phone,Home Phone,Other

With no LOGIN, chfn operates on the account of the user running it.

If no field option is given, chfn prompts for each field it is allowed to change, showing the current value in brackets. Pressing ENTER at a prompt keeps the current value; an empty run makes no change and exits successfully. Supply a field option to change one field without being prompted for the others, or an empty argument (for example -r '') to clear a field.

PERMISSIONS

A normal user may only change their own finger information, and only the fields listed in CHFN_RESTRICT (see below). The superuser may change any field of any account.

chfn is normally installed setuid-root, so it establishes who is asking before it writes: a non-root caller must authenticate through the chfn PAM service (typically /etc/pam.d/chfn, where pam_rootok admits root and the system authentication stack prompts everyone else). Authentication happens after the new values have been checked and accepted, so a rejected value never costs the caller a password prompt.

A build without PAM support cannot verify the caller, so it refuses every non-root invocation rather than applying an unauthenticated change.

OPTIONS

-f, --full-name FULL_NAME
Change the user's full name.
-h, --home-phone HOME_PHONE
Change the user's home phone number.
-o, --other OTHER
Change the user's other GECOS information. Only the superuser may set this field, in any configuration; CHFN_RESTRICT cannot delegate it.
-r, --room ROOM
Change the user's room number.
-R, --root CHROOT_DIR
Apply changes in the CHROOT_DIR directory. Only the superuser may use this option: it points a setuid-root program at files of the caller's choosing.
-w, --work-phone WORK_PHONE
Change the user's office phone number.
--help
Print a usage summary and exit.
--version
Print the version and exit.

VALUE RESTRICTIONS

Every value is checked before anything is locked or written. A value is rejected if it contains a colon, a newline, or any other control character: those would corrupt the record and could inject a second account into /etc/passwd.

A comma or an equals sign is additionally rejected in every field except other, the final sub-field. A comma would be read back as a sub-field separator, silently shifting every following field.

CONFIGURATION

CHFN_RESTRICT in /etc/login.defs (see login.defs(5)) lists which fields a non-root user may change, as a string of letters:

Letter Field
f full name
r room number
w work phone
h home phone

The value yes is equivalent to rwh. If CHFN_RESTRICT is absent, or /etc/login.defs cannot be read, non-root users may change nothing and only the superuser can make changes.

In interactive mode a field the caller may not change is not prompted for, so no answer is collected only to be refused. A non-root caller allowed no field at all is told so immediately.

EXIT STATUS

0
Success, including an interactive run in which every prompt was answered with ENTER.
1
Permission denied, an invalid value, a missing account, or a failure to read or write /etc/passwd.

FILES

/etc/passwd
User account information.
/etc/login.defs
Shadow password suite configuration, read for CHFN_RESTRICT.
/etc/pam.d/chfn
PAM service used to authenticate a non-root caller.

EXAMPLES

Change your own full name and office phone in one command:

$ chfn -f 'Ada Lovelace' -w '+44 20 7946 0958'

Review and change every field you are allowed to change:

$ chfn
Changing the user information for ada
Enter the new value, or press ENTER for the default
	Full Name [Ada Lovelace]:
	Room Number [C-101]: C-204
	Work Phone [+44 20 7946 0958]:
	Home Phone []:

Only the room number is written; the fields answered with ENTER keep their current values.

Clear another user's room number as root:

# chfn -r '' ada

SEE ALSO

chsh(1), finger(1), login.defs(5), passwd(5)