Users and permissions: accounts, login, enforced file permissions, setuid, su, passwd, doas
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
This commit is contained in:
parent
b3f72cd95e
commit
020aa6113c
34 changed files with 1342 additions and 120 deletions
221
cmd/su/main.c
Normal file
221
cmd/su/main.c
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
// 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;
|
||||
}
|
||||
Loading…
Reference in a new issue