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
|
|
@ -58,6 +58,14 @@
|
|||
#define DEVFS_PTS0 8192ULL
|
||||
#define DEVFS_PTM0 12288ULL
|
||||
|
||||
// Groups, by the numbers Alpine's /etc/group gives them.
|
||||
#define G_ROOT 0
|
||||
#define G_TTY 5
|
||||
#define G_DISK 6
|
||||
#define G_AUDIO 18
|
||||
#define G_INPUT 23
|
||||
#define G_VIDEO 27
|
||||
|
||||
// What udev adds to /dev: directories (/dev/input/by-path, /dev/disk/...) and
|
||||
// symlinks (/dev/fd, /dev/stdin, /dev/char/13:64, /dev/disk/by-id/...). The
|
||||
// device nodes themselves stay the kernel's; this is a small table of names
|
||||
|
|
@ -95,6 +103,71 @@ extras_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
// What chmod and chown have done to the kernel's nodes. A node is built from
|
||||
// the table above every time its inode is loaded, so a change has to be kept
|
||||
// somewhere that outlives the inode: here, by inode number, applied on the way
|
||||
// in. login claims its terminal this way, and udev sets modes on input nodes.
|
||||
#define NOVR 128
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
int ready;
|
||||
struct { uint64 inum; uint mode, uid, gid; } e[NOVR];
|
||||
} ovr;
|
||||
|
||||
static void
|
||||
ovr_init(void)
|
||||
{
|
||||
if (!ovr.ready) {
|
||||
initlock(&ovr.lock, "devfsovr");
|
||||
ovr.ready = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ovr_apply(struct inode *ip)
|
||||
{
|
||||
ovr_init();
|
||||
acquire(&ovr.lock);
|
||||
for (int i = 0; i < NOVR; i++)
|
||||
if (ovr.e[i].inum == ip->inum) {
|
||||
ip->mode = ovr.e[i].mode;
|
||||
ip->uid = ovr.e[i].uid;
|
||||
ip->gid = ovr.e[i].gid;
|
||||
break;
|
||||
}
|
||||
release(&ovr.lock);
|
||||
}
|
||||
|
||||
static void
|
||||
ovr_set(uint64 inum, uint mode, uint uid, uint gid)
|
||||
{
|
||||
int slot = -1;
|
||||
|
||||
ovr_init();
|
||||
acquire(&ovr.lock);
|
||||
for (int i = 0; i < NOVR; i++) {
|
||||
if (ovr.e[i].inum == inum) { slot = i; break; }
|
||||
if (slot < 0 && ovr.e[i].inum == 0)
|
||||
slot = i;
|
||||
}
|
||||
if (slot >= 0) {
|
||||
ovr.e[slot].inum = inum;
|
||||
ovr.e[slot].mode = mode;
|
||||
ovr.e[slot].uid = uid;
|
||||
ovr.e[slot].gid = gid;
|
||||
}
|
||||
release(&ovr.lock);
|
||||
}
|
||||
|
||||
// A new pseudo-terminal's slave belongs to whoever opened ptmx, in group tty,
|
||||
// readable and writable by the owner and writable by the group -- what grantpt
|
||||
// does on Linux.
|
||||
void
|
||||
devfs_pty_claim(int n, uint uid)
|
||||
{
|
||||
ovr_set(DEVFS_PTS0 + (uint64)n, 0620, uid, G_TTY);
|
||||
}
|
||||
|
||||
static int
|
||||
extra_of(uint64 inum)
|
||||
{
|
||||
|
|
@ -115,8 +188,11 @@ struct devnode {
|
|||
short major;
|
||||
short minor;
|
||||
short dir;
|
||||
ushort mode; // permission bits a fresh boot starts with
|
||||
ushort gid; // owner is always root; the group by name
|
||||
};
|
||||
|
||||
|
||||
// The devices this kernel implements, in the order they appear in a listing.
|
||||
//
|
||||
// Minor numbers under major 1 follow Linux's mem-major layout where it costs
|
||||
|
|
@ -125,65 +201,65 @@ struct devnode {
|
|||
//
|
||||
// The majors are devsw[] indices and nothing else; see kernel/dev/cdev.c.
|
||||
static const struct devnode devnodes[] = {
|
||||
{ "console", 1, 1, D_ROOT },
|
||||
{ "null", 1, 2, D_ROOT },
|
||||
{ "keyboard",1, 3, D_ROOT },
|
||||
{ "zero", 1, 5, D_ROOT },
|
||||
{ "full", 1, 7, D_ROOT },
|
||||
{ "random", 1, 8, D_ROOT },
|
||||
{ "urandom", 1, 9, D_ROOT },
|
||||
{ "console", 1, 1, D_ROOT , 0600, G_ROOT },
|
||||
{ "null", 1, 2, D_ROOT , 0666, G_ROOT },
|
||||
{ "keyboard",1, 3, D_ROOT , 0660, G_INPUT },
|
||||
{ "zero", 1, 5, D_ROOT , 0666, G_ROOT },
|
||||
{ "full", 1, 7, D_ROOT , 0666, G_ROOT },
|
||||
{ "random", 1, 8, D_ROOT , 0666, G_ROOT },
|
||||
{ "urandom", 1, 9, D_ROOT , 0666, G_ROOT },
|
||||
/* /dev/tty is the caller's *controlling* terminal, which is a different
|
||||
thing from the console and used to be the same thing only because there
|
||||
was one terminal. Minor 10 resolves through proc->ctty; see
|
||||
vt_for_minor() in kernel/core/console.c. */
|
||||
{ "tty", 1, 10, D_ROOT },
|
||||
{ "tty", 1, 10, D_ROOT , 0666, G_ROOT },
|
||||
/* The terminal on screen, as on Linux. Xorg opens it to find a free VT and
|
||||
switch to it, and without one ran on no VT at all: the console went on
|
||||
drawing into the framebuffer over the X screen. Any minor that is not a
|
||||
numbered terminal or the controlling one resolves to vt_current(). */
|
||||
{ "tty0", 1, 0, D_ROOT },
|
||||
{ "tty1", 1, 11, D_ROOT },
|
||||
{ "tty2", 1, 12, D_ROOT },
|
||||
{ "tty3", 1, 13, D_ROOT },
|
||||
{ "tty4", 1, 14, D_ROOT },
|
||||
{ "tty5", 1, 15, D_ROOT },
|
||||
{ "tty6", 1, 16, D_ROOT },
|
||||
{ "tty7", 1, 17, D_ROOT },
|
||||
{ "tty8", 1, 18, D_ROOT },
|
||||
{ "tty9", 1, 19, D_ROOT },
|
||||
{ "tty10", 1, 20, D_ROOT },
|
||||
{ "tty11", 1, 21, D_ROOT },
|
||||
{ "tty12", 1, 22, D_ROOT },
|
||||
{ "stdin", 1, 1, D_ROOT },
|
||||
{ "stdout", 1, 1, D_ROOT },
|
||||
{ "stderr", 1, 1, D_ROOT },
|
||||
{ "fb0", 2, 0, D_ROOT },
|
||||
{ "pci", 9, 0, D_ROOT },
|
||||
{ "dbg", 10, 0, D_ROOT },
|
||||
{ "tty0", 1, 0, D_ROOT , 0660, G_TTY },
|
||||
{ "tty1", 1, 11, D_ROOT , 0660, G_TTY },
|
||||
{ "tty2", 1, 12, D_ROOT , 0660, G_TTY },
|
||||
{ "tty3", 1, 13, D_ROOT , 0660, G_TTY },
|
||||
{ "tty4", 1, 14, D_ROOT , 0660, G_TTY },
|
||||
{ "tty5", 1, 15, D_ROOT , 0660, G_TTY },
|
||||
{ "tty6", 1, 16, D_ROOT , 0660, G_TTY },
|
||||
{ "tty7", 1, 17, D_ROOT , 0660, G_TTY },
|
||||
{ "tty8", 1, 18, D_ROOT , 0660, G_TTY },
|
||||
{ "tty9", 1, 19, D_ROOT , 0660, G_TTY },
|
||||
{ "tty10", 1, 20, D_ROOT , 0660, G_TTY },
|
||||
{ "tty11", 1, 21, D_ROOT , 0660, G_TTY },
|
||||
{ "tty12", 1, 22, D_ROOT , 0660, G_TTY },
|
||||
{ "stdin", 1, 1, D_ROOT , 0666, G_ROOT },
|
||||
{ "stdout", 1, 1, D_ROOT , 0666, G_ROOT },
|
||||
{ "stderr", 1, 1, D_ROOT , 0666, G_ROOT },
|
||||
{ "fb0", 2, 0, D_ROOT , 0660, G_VIDEO },
|
||||
{ "pci", 9, 0, D_ROOT , 0600, G_ROOT },
|
||||
{ "dbg", 10, 0, D_ROOT , 0600, G_ROOT },
|
||||
// The same device, answering only the views that read. Minor 1 rather than
|
||||
// a flag, because the minor is the one piece of per-open state a character
|
||||
// device already sees -- so which one you opened *is* the permission, and
|
||||
// nothing has to be remembered between the write and the read. See
|
||||
// include/core/dbg.h and cmd/schdbgd.
|
||||
{ "dbgro", 10, 1, D_ROOT },
|
||||
{ "netdev", 11, 0, D_ROOT },
|
||||
{ "dbgro", 10, 1, D_ROOT , 0644, G_ROOT },
|
||||
{ "netdev", 11, 0, D_ROOT , 0600, G_ROOT },
|
||||
// Audio, spelled the way UNIX spells it. See include/dev/audio.h.
|
||||
{ "dsp", 12, 0, D_ROOT },
|
||||
{ "mouse", 3, 1, D_ROOT },
|
||||
{ "dsp", 12, 0, D_ROOT , 0660, G_AUDIO },
|
||||
{ "mouse", 3, 1, D_ROOT , 0660, G_INPUT },
|
||||
// Opening it makes a new pseudo-terminal pair; see kernel/dev/pty.c.
|
||||
{ "ptmx", PTMX_MAJOR, 0, D_ROOT },
|
||||
{ "ptmx", PTMX_MAJOR, 0, D_ROOT , 0666, G_ROOT },
|
||||
|
||||
// Raw disks. Created whether or not a drive is present: opening one with no
|
||||
// disk behind it fails with ENXIO, which is a better answer than a missing
|
||||
// path -- and with no node at all, `echo x > /dev/hdb` silently creates an
|
||||
// ordinary file of that name and appears to work.
|
||||
{ "hda", 8, 0, D_ROOT },
|
||||
{ "hdb", 8, 1, D_ROOT },
|
||||
{ "hda", 8, 0, D_ROOT , 0660, G_DISK },
|
||||
{ "hdb", 8, 1, D_ROOT , 0660, G_DISK },
|
||||
|
||||
// Raw input events, Linux evdev layout. The minor is the device index, so
|
||||
// event0 is the keyboard and event1 the mouse.
|
||||
{ "event0", 7, 0, D_INPUT },
|
||||
{ "event1", 7, 1, D_INPUT },
|
||||
{ "event0", 7, 0, D_INPUT , 0660, G_INPUT },
|
||||
{ "event1", 7, 1, D_INPUT , 0660, G_INPUT },
|
||||
};
|
||||
|
||||
#define NDEVNODES ((int)(sizeof(devnodes) / sizeof(devnodes[0])))
|
||||
|
|
@ -206,6 +282,7 @@ devfs_iload(struct inode *ip)
|
|||
ip->size = 0;
|
||||
|
||||
int x = extra_of(ip->inum);
|
||||
ip->mode = 0;
|
||||
if (x >= 0) {
|
||||
extras_init();
|
||||
acquire(&extras.lock);
|
||||
|
|
@ -240,20 +317,33 @@ devfs_iload(struct inode *ip)
|
|||
ip->nlink = 1;
|
||||
ip->major = devnodes[i].major;
|
||||
ip->minor = devnodes[i].minor;
|
||||
ip->mode = devnodes[i].mode;
|
||||
ip->gid = devnodes[i].gid;
|
||||
} else if (ip->inum >= DEVFS_BLK0 &&
|
||||
blkdev_present((uint)(ip->inum - DEVFS_BLK0))) {
|
||||
ip->type = T_DEV;
|
||||
ip->nlink = 1;
|
||||
ip->major = 8;
|
||||
ip->minor = (short)(ip->inum - DEVFS_BLK0);
|
||||
ip->mode = 0660;
|
||||
ip->gid = G_DISK;
|
||||
} else {
|
||||
ip->type = 0; /* no such node */
|
||||
ip->nlink = 0;
|
||||
}
|
||||
if (x < 0 && ip->type != 0)
|
||||
ovr_apply(ip);
|
||||
ip->valid = 1;
|
||||
}
|
||||
|
||||
static void devfs_istore(struct inode *ip) { (void)ip; }
|
||||
// iupdate() lands here after a chmod or chown; see ovr above. Directories and
|
||||
// udev's links keep nothing.
|
||||
static void
|
||||
devfs_istore(struct inode *ip)
|
||||
{
|
||||
if (extra_of(ip->inum) < 0 && ip->type == T_DEV)
|
||||
ovr_set(ip->inum, ip->mode, ip->uid, ip->gid);
|
||||
}
|
||||
static void
|
||||
devfs_ifree(struct inode *ip)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "arch/mmu.h"
|
||||
#include "core/proc.h"
|
||||
#include "core/defs.h"
|
||||
#include "uapi/stat.h"
|
||||
#include "arch/x86.h"
|
||||
#include "core/elf.h"
|
||||
#include "uapi/fcntl.h"
|
||||
|
|
@ -403,6 +404,10 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
begin_op();
|
||||
int in_op = 1; // the error path closes the transaction only once
|
||||
int ret = -1;
|
||||
// What the file's mode asks for, read while it is still locked: a program
|
||||
// with the set-user-id bit runs as its owner, set-group-id as its group.
|
||||
int want_suid = 0, want_sgid = 0;
|
||||
uint file_uid = 0, file_gid = 0;
|
||||
|
||||
int err;
|
||||
if((ip = nameifollow(0, path, &err)) == 0){
|
||||
|
|
@ -415,6 +420,20 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
ilock(ip);
|
||||
pgdir = 0;
|
||||
|
||||
// To run a file is to have execute permission on it, and it has to be a
|
||||
// plain file: a directory has the bit and is not a program.
|
||||
if(ip->type == T_DIR || ip->type == T_DEV || inode_permission(ip, 1, 0) < 0){
|
||||
ret = -EACCES;
|
||||
goto bad;
|
||||
}
|
||||
{
|
||||
uint m = inode_mode_bits(ip);
|
||||
want_suid = (m & 04000) != 0;
|
||||
want_sgid = (m & 02000) != 0 && (m & 010) != 0;
|
||||
file_uid = ip->uid;
|
||||
file_gid = ip->gid;
|
||||
}
|
||||
|
||||
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf)){
|
||||
ret = -ENOEXEC; // too short to be anything we can run
|
||||
goto bad;
|
||||
|
|
@ -572,6 +591,15 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
myproc()->sz = sz;
|
||||
pgdir = 0; // newmm owns it now; not bad:'s to free
|
||||
|
||||
// The new image's identity. The saved ids become the effective ones, which
|
||||
// is what lets a program drop its privilege and take it back (su, sudo).
|
||||
if (want_suid)
|
||||
myproc()->euid = file_uid;
|
||||
if (want_sgid)
|
||||
myproc()->egid = file_gid;
|
||||
myproc()->suid = myproc()->euid;
|
||||
myproc()->sgid = myproc()->egid;
|
||||
|
||||
// New address space: reset all per-process mmap/signal/TLS state.
|
||||
// sig_handler/sig_restorer point into the *old* image — POSIX resets
|
||||
// handled signals to default on exec, and keeping them would make the
|
||||
|
|
|
|||
|
|
@ -361,6 +361,82 @@ fs_readonly(uint dev)
|
|||
return SCHFS_IS_SNAP_DEV(dev) || (FAT_IS_DEV(dev) && fat_readonly(dev));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Permissions
|
||||
// ===========================================================================
|
||||
|
||||
// Is `gid` the caller's group, effective or supplementary? `real` asks about
|
||||
// the real group instead of the effective one -- access(2) does.
|
||||
int
|
||||
in_group(uint gid, int real)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
|
||||
if (p == 0)
|
||||
return 1;
|
||||
if (gid == (real ? p->gid : p->egid))
|
||||
return 1;
|
||||
for (int i = 0; i < p->ngroups; i++)
|
||||
if (p->groups[i] == gid)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The permission bits of an inode, 07777, as stat reports them. A filesystem
|
||||
// that keeps real ones (SchFS) supplies them; the rest are made up from the
|
||||
// type, which is what stat has always done. A mode that was recorded as "no
|
||||
// bits" is 01000000, which masks to zero here.
|
||||
uint
|
||||
inode_mode_bits(struct inode *ip)
|
||||
{
|
||||
if (ip->mode)
|
||||
return ip->mode & 07777;
|
||||
switch (ip->type) {
|
||||
case T_DIR: return FAT_IS_DEV(ip->dev) ? 0777 : 0755;
|
||||
case T_DEV: return 0666;
|
||||
case T_SYMLINK: return 0777;
|
||||
case T_SOCK: return 0755;
|
||||
}
|
||||
// A volume that cannot say who owns anything is everyone's: FAT on a USB
|
||||
// stick was usable by whoever was logged in, and still is.
|
||||
if (FAT_IS_DEV(ip->dev))
|
||||
return 0777;
|
||||
return (ip->rwmode & 2) ? 0755 : 0555;
|
||||
}
|
||||
|
||||
// May the caller do `mask` (4 read, 2 write, 1 execute or search) to this
|
||||
// inode? 0, or -EACCES. The caller holds the inode lock or knows it will not
|
||||
// change under it.
|
||||
//
|
||||
// Root may read and write anything, and may execute a file only if somebody
|
||||
// can -- the same rule Linux applies, so that a data file does not become a
|
||||
// program because root asked.
|
||||
int
|
||||
inode_permission(struct inode *ip, int mask, int real)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
uint m = inode_mode_bits(ip);
|
||||
uint who = p ? (real ? p->uid : p->euid) : 0;
|
||||
|
||||
if (p == 0 || who == 0) {
|
||||
if ((mask & 1) && ip->type != T_DIR && !(m & 0111))
|
||||
return -EACCES;
|
||||
return 0;
|
||||
}
|
||||
if (who == ip->uid)
|
||||
m >>= 6;
|
||||
else if (in_group(ip->gid, real))
|
||||
m >>= 3;
|
||||
return ((m & 7) & (uint)mask) == (uint)mask ? 0 : -EACCES;
|
||||
}
|
||||
|
||||
// Superuser-only operations: mount, reboot, settimeofday and the rest.
|
||||
int
|
||||
capable(void)
|
||||
{
|
||||
return myproc() == 0 || myproc()->euid == 0;
|
||||
}
|
||||
|
||||
static const struct fs_ops *
|
||||
ops_for(uint dev)
|
||||
{
|
||||
|
|
@ -626,6 +702,7 @@ ilock(struct inode *ip)
|
|||
|
||||
if (ip->valid == 0) {
|
||||
ip->mode = 0;
|
||||
ip->uid = ip->gid = 0;
|
||||
ip->ops->iload(ip);
|
||||
}
|
||||
}
|
||||
|
|
@ -1970,6 +2047,15 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
|
|||
*err = ENOTDIR;
|
||||
goto fail;
|
||||
}
|
||||
// Searching a directory takes its execute bit. The directory that holds
|
||||
// the final name is searched too, even when the caller only wants its
|
||||
// inode: nothing under a directory you cannot enter is yours to name.
|
||||
if (inode_permission(ip, 1, 0) < 0) {
|
||||
iunlockput(ip);
|
||||
if (err)
|
||||
*err = EACCES;
|
||||
goto fail;
|
||||
}
|
||||
if (nameiparent && *spath == '\0') {
|
||||
iunlock(ip);
|
||||
goto out;
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ schfs_vfs_iload(struct inode *ip)
|
|||
ip->size = in.size;
|
||||
ip->rwmode = RW;
|
||||
ip->mode = in.mode & 07777; // 0 on disk: never recorded
|
||||
ip->uid = in.uid;
|
||||
ip->gid = in.gid;
|
||||
ip->supermode = 0;
|
||||
ip->showmode = 1;
|
||||
ip->atime = (uint)in.atime;
|
||||
|
|
@ -137,6 +139,8 @@ schfs_vfs_istore(struct inode *ip)
|
|||
|
||||
if (ip->mode)
|
||||
in.mode = ip->mode & 07777;
|
||||
in.uid = ip->uid;
|
||||
in.gid = ip->gid;
|
||||
in.type = type_from_vfs(ip->type);
|
||||
in.rdev_major = (uint16_t)ip->major;
|
||||
in.rdev_minor = (uint16_t)ip->minor;
|
||||
|
|
|
|||
Loading…
Reference in a new issue