The kernel now checks owner/group/other on open, create, unlink, rename, exec and path search; honours set-user-id and set-group-id; restricts kill, mount, reboot and mknod; keeps supplementary groups; and implements chown. devtmpfs nodes carry owners and modes that chmod/chown keep. Terminals run login, the image has root and schos, X starts as the ordinary user. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
221 lines
6.5 KiB
C
221 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// su [-|-l] [-p] [-c COMMAND] [USER]
|
|
//
|
|
// Run a shell, or one command, as another user -- root by default. Setuid root,
|
|
// and a program of its own rather than a toybox applet: toybox is one binary
|
|
// under every name, and a setuid copy of it can be asked for any of its other
|
|
// applets by naming them, all as root.
|
|
//
|
|
// The rules are the ones su has always had, kept short:
|
|
//
|
|
// - root may become anyone, with no password;
|
|
// - anyone else is asked for the target's password, and gets three tries;
|
|
// - becoming root takes membership in `wheel` first, whatever the password
|
|
// is -- an account with no password is otherwise a door for every user.
|
|
//
|
|
// -, -l a login shell: a clean environment, the target's home, argv[0] "-sh"
|
|
// -p keep the environment (HOME, SHELL, USER and LOGNAME are still set
|
|
// unless the target is root)
|
|
// -c CMD run CMD with the shell's -c rather than an interactive shell
|
|
|
|
#define _GNU_SOURCE
|
|
#include <crypt.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <shadow.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
static int
|
|
in_wheel(uid_t uid, gid_t gid)
|
|
{
|
|
struct group *g = getgrnam("wheel");
|
|
struct passwd *pw = getpwuid(uid);
|
|
|
|
if (!g)
|
|
return 0;
|
|
if (gid == g->gr_gid)
|
|
return 1;
|
|
for (char **m = g->gr_mem; pw && m && *m; m++)
|
|
if (strcmp(*m, pw->pw_name) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// A line from the terminal with echo off. The password is read from the
|
|
// controlling terminal, not stdin, so a script cannot feed it.
|
|
static int
|
|
read_password(const char *prompt, char *buf, size_t n)
|
|
{
|
|
int fd = open("/dev/tty", O_RDWR | O_NOCTTY);
|
|
struct termios t, old;
|
|
size_t i = 0;
|
|
char c;
|
|
|
|
if (fd < 0)
|
|
return -1;
|
|
if (tcgetattr(fd, &old) == 0) {
|
|
t = old;
|
|
t.c_lflag &= ~(ECHO | ECHONL);
|
|
tcsetattr(fd, TCSAFLUSH, &t);
|
|
}
|
|
if (write(fd, prompt, strlen(prompt)) < 0) {}
|
|
while (read(fd, &c, 1) == 1 && c != '\n' && c != '\r')
|
|
if (i + 1 < n)
|
|
buf[i++] = c;
|
|
buf[i] = 0;
|
|
tcsetattr(fd, TCSAFLUSH, &old);
|
|
if (write(fd, "\n", 1) < 0) {}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
// Does `pw` accept this password? An empty hash accepts anything, a locked one
|
|
// ("*", "!...") nothing.
|
|
static int
|
|
password_ok(const struct passwd *pw, const char *pass)
|
|
{
|
|
const char *hash = pw->pw_passwd;
|
|
struct spwd *sp;
|
|
char *enc;
|
|
|
|
if (hash[0] == 'x' && hash[1] == 0) {
|
|
sp = getspnam(pw->pw_name);
|
|
if (!sp)
|
|
return 0;
|
|
hash = sp->sp_pwdp;
|
|
}
|
|
if (hash[0] == 0)
|
|
return 1;
|
|
if (hash[0] == '*' || hash[0] == '!')
|
|
return 0;
|
|
enc = crypt(pass, hash);
|
|
return enc && strcmp(enc, hash) == 0;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
const char *target = "root", *cmd = 0;
|
|
int login = 0, keep = 0, i;
|
|
struct passwd *pw, *me;
|
|
uid_t uid = getuid();
|
|
char pass[256];
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (!strcmp(argv[i], "-") || !strcmp(argv[i], "-l"))
|
|
login = 1;
|
|
else if (!strcmp(argv[i], "-p"))
|
|
keep = 1;
|
|
else if (!strcmp(argv[i], "-c") && i + 1 < argc)
|
|
cmd = argv[++i];
|
|
else if (argv[i][0] == '-' && argv[i][1]) {
|
|
fprintf(stderr, "usage: su [-|-l] [-p] [-c COMMAND] [USER]\n");
|
|
return 2;
|
|
} else
|
|
target = argv[i];
|
|
}
|
|
|
|
if (geteuid() != 0) {
|
|
fprintf(stderr, "su: must be setuid root\n");
|
|
return 1;
|
|
}
|
|
// getpwnam and getpwuid return the same static buffer, so the second call
|
|
// overwrites the first answer. Keep our own copy of the one that matters.
|
|
if (!(pw = getpwnam(target))) {
|
|
fprintf(stderr, "su: unknown user %s\n", target);
|
|
return 1;
|
|
}
|
|
{
|
|
static struct passwd copy;
|
|
copy = *pw;
|
|
copy.pw_name = strdup(pw->pw_name);
|
|
copy.pw_passwd = strdup(pw->pw_passwd);
|
|
copy.pw_dir = strdup(pw->pw_dir);
|
|
copy.pw_shell = strdup(pw->pw_shell);
|
|
pw = ©
|
|
}
|
|
me = getpwuid(uid);
|
|
|
|
if (uid != 0) {
|
|
int tries, ok = 0;
|
|
|
|
if (pw->pw_uid == 0 && !in_wheel(uid, getgid())) {
|
|
fprintf(stderr, "su: %s is not in the wheel group\n",
|
|
me ? me->pw_name : "you");
|
|
return 1;
|
|
}
|
|
for (tries = 0; tries < 3 && !ok; tries++) {
|
|
if (pw->pw_passwd[0] == 0 ||
|
|
(pw->pw_passwd[0] == 'x' && getspnam(pw->pw_name) &&
|
|
getspnam(pw->pw_name)->sp_pwdp[0] == 0)) {
|
|
ok = 1;
|
|
break;
|
|
}
|
|
if (read_password("Password: ", pass, sizeof pass) < 0) {
|
|
fprintf(stderr, "su: no terminal\n");
|
|
return 1;
|
|
}
|
|
ok = password_ok(pw, pass);
|
|
memset(pass, 0, sizeof pass);
|
|
if (!ok) {
|
|
sleep(2);
|
|
fprintf(stderr, "su: incorrect password\n");
|
|
}
|
|
}
|
|
if (!ok)
|
|
return 1;
|
|
}
|
|
|
|
if (initgroups(pw->pw_name, pw->pw_gid) < 0 ||
|
|
setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
|
|
fprintf(stderr, "su: cannot become %s: %s\n", pw->pw_name, strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
const char *shell = pw->pw_shell && *pw->pw_shell ? pw->pw_shell : "/bin/sh";
|
|
char *sh_argv[5];
|
|
int n = 0;
|
|
char arg0[256];
|
|
|
|
if (login) {
|
|
clearenv();
|
|
setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1);
|
|
if (getenv("TERM") == 0)
|
|
setenv("TERM", "linux", 1);
|
|
}
|
|
if (login || !keep || pw->pw_uid != 0) {
|
|
setenv("HOME", pw->pw_dir, 1);
|
|
setenv("SHELL", shell, 1);
|
|
setenv("USER", pw->pw_name, 1);
|
|
setenv("LOGNAME", pw->pw_name, 1);
|
|
}
|
|
if (login && chdir(pw->pw_dir) < 0)
|
|
chdir("/");
|
|
|
|
if (login) {
|
|
const char *base = strrchr(shell, '/');
|
|
snprintf(arg0, sizeof arg0, "-%s", base ? base + 1 : shell);
|
|
sh_argv[n++] = arg0;
|
|
} else {
|
|
const char *base = strrchr(shell, '/');
|
|
sh_argv[n++] = (char *)(base ? base + 1 : shell);
|
|
}
|
|
if (cmd) {
|
|
sh_argv[n++] = "-c";
|
|
sh_argv[n++] = (char *)cmd;
|
|
}
|
|
sh_argv[n] = 0;
|
|
execv(shell, sh_argv);
|
|
fprintf(stderr, "su: %s: %s\n", shell, strerror(errno));
|
|
}
|
|
return 1;
|
|
}
|