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
|
|
@ -144,6 +144,7 @@ ptmx_open(struct inode *ip)
|
|||
ptys[n].used = 0;
|
||||
return 0;
|
||||
}
|
||||
devfs_pty_claim(n, myproc() ? myproc()->euid : 0);
|
||||
iunlockput(ip);
|
||||
ilock(m);
|
||||
return m;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -723,6 +723,14 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|||
// look for it. Same contract as a pipe with no reader: a signal, then an
|
||||
// error. A process that writes to a dead connection and is not told about
|
||||
// it spins forever believing it is making progress.
|
||||
//
|
||||
// A datagram socket is not a connection, so having no peer is not a broken
|
||||
// pipe: syslog() sends to /dev/log whether or not anything listens, and a
|
||||
// signal for that killed doas.
|
||||
if(s->domain == AF_UNIX && s->type == SOCK_DGRAM && s->peer == 0){
|
||||
release(&stab.lock);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
if(s->peer == 0 || s->wr_shut){
|
||||
release(&stab.lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
|
|
|
|||
|
|
@ -532,6 +532,7 @@ found:
|
|||
p->sid = 0;
|
||||
p->uid = p->euid = p->suid = 0;
|
||||
p->gid = p->egid = p->sgid = 0;
|
||||
p->ngroups = 0;
|
||||
|
||||
release(&ptable.lock);
|
||||
|
||||
|
|
@ -760,6 +761,8 @@ fork(void)
|
|||
np->ctty = myproc()->ctty;
|
||||
np->uid = myproc()->uid; np->euid = myproc()->euid; np->suid = myproc()->suid;
|
||||
np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid;
|
||||
np->ngroups = myproc()->ngroups;
|
||||
memmove(np->groups, myproc()->groups, sizeof(np->groups));
|
||||
|
||||
np->cwd = idup(myproc()->cwd);
|
||||
np->root = myproc()->root ? idup(myproc()->root) : 0;
|
||||
|
|
@ -1167,6 +1170,8 @@ clone_proc(uint64 flags, uint64 stack, uint64 ptid, uint64 ctid, uint64 tls)
|
|||
np->ctty = cur->ctty;
|
||||
np->uid = cur->uid; np->euid = cur->euid; np->suid = cur->suid;
|
||||
np->gid = cur->gid; np->egid = cur->egid; np->sgid = cur->sgid;
|
||||
np->ngroups = cur->ngroups;
|
||||
memmove(np->groups, cur->groups, sizeof(np->groups));
|
||||
np->cwd = idup(cur->cwd);
|
||||
np->root = cur->root ? idup(cur->root) : 0;
|
||||
memmove(np->cwd_path, cur->cwd_path, sizeof(cur->cwd_path));
|
||||
|
|
@ -2614,8 +2619,21 @@ sig_to_threads(struct proc *p, int sig)
|
|||
}
|
||||
}
|
||||
|
||||
int
|
||||
sigsend(int pid, int sig)
|
||||
// May `from` signal `to`? Root may; anyone else only a process whose real or
|
||||
// saved user id is one of their own ids -- kill(2)'s rule.
|
||||
static int
|
||||
sig_permitted(struct proc *from, struct proc *to)
|
||||
{
|
||||
if(from == 0 || from->euid == 0)
|
||||
return 1;
|
||||
return from->uid == to->uid || from->uid == to->suid ||
|
||||
from->euid == to->uid || from->euid == to->suid;
|
||||
}
|
||||
|
||||
// `check` is for a signal a process sends on purpose, by kill(2). The kernel's
|
||||
// own -- SIGPIPE, the terminal's SIGINT -- is nobody's to refuse.
|
||||
static int
|
||||
sigsend_x(int pid, int sig, int check)
|
||||
{
|
||||
struct proc *p;
|
||||
|
||||
|
|
@ -2625,6 +2643,10 @@ sigsend(int pid, int sig)
|
|||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){
|
||||
if(check && !sig_permitted(myproc(), p)){
|
||||
release(&ptable.lock);
|
||||
return -2;
|
||||
}
|
||||
if(init_ignores(p, sig)){
|
||||
release(&ptable.lock);
|
||||
return 0; // silently dropped, as POSIX requires
|
||||
|
|
@ -2652,16 +2674,29 @@ sigsend(int pid, int sig)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
sigsend(int pid, int sig)
|
||||
{
|
||||
return sigsend_x(pid, sig, 0);
|
||||
}
|
||||
|
||||
// -1 if there is no such process, -2 if there is and the caller may not.
|
||||
int
|
||||
sigsend_user(int pid, int sig)
|
||||
{
|
||||
return sigsend_x(pid, sig, 1);
|
||||
}
|
||||
|
||||
// Deliver a signal to every member of a process group. This is what job
|
||||
// control actually needs: a shell sends SIGINT/SIGTSTP/SIGHUP to a *group*,
|
||||
// never to individual pids. sys_kill() with pid <= 0 routes here.
|
||||
//
|
||||
// Returns 0 if at least one process was signalled, -1 if the group is empty.
|
||||
int
|
||||
sigsend_pgrp(int pgid, int sig)
|
||||
static int
|
||||
sigsend_pgrp_x(int pgid, int sig, int check)
|
||||
{
|
||||
struct proc *p;
|
||||
int found = 0;
|
||||
int found = 0, denied = 0;
|
||||
|
||||
if(sig < 0 || sig >= 32)
|
||||
return -1;
|
||||
|
|
@ -2672,6 +2707,10 @@ sigsend_pgrp(int pgid, int sig)
|
|||
continue;
|
||||
if(p->pgid != pgid)
|
||||
continue;
|
||||
if(check && !sig_permitted(myproc(), p)){
|
||||
denied = 1;
|
||||
continue;
|
||||
}
|
||||
found = 1;
|
||||
if(sig == 0)
|
||||
continue; // signal 0 is an existence probe only
|
||||
|
|
@ -2701,7 +2740,19 @@ sigsend_pgrp(int pgid, int sig)
|
|||
}
|
||||
release(&ptable.lock);
|
||||
|
||||
return found ? 0 : -1;
|
||||
return found ? 0 : denied ? -2 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
sigsend_pgrp(int pgid, int sig)
|
||||
{
|
||||
return sigsend_pgrp_x(pgid, sig, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sigsend_pgrp_user(int pgid, int sig)
|
||||
{
|
||||
return sigsend_pgrp_x(pgid, sig, 1);
|
||||
}
|
||||
|
||||
// Look up a process's process-group id. Returns -1 if no such live process.
|
||||
|
|
|
|||
|
|
@ -120,9 +120,11 @@ stati_linux(struct inode *ip, struct linux_stat *st)
|
|||
}
|
||||
// A filesystem that keeps real permission bits -- SchFS does -- reports
|
||||
// those instead. tmux, ssh and gpg refuse a directory others can read.
|
||||
if (ip->mode)
|
||||
perm_bits = ip->mode & 07777;
|
||||
(void)perm_bits;
|
||||
perm_bits = inode_mode_bits(ip);
|
||||
st->st_mode = type_bits | perm_bits;
|
||||
st->st_uid = ip->uid;
|
||||
st->st_gid = ip->gid;
|
||||
|
||||
// Which device this node actually is. Left at zero until now, so every
|
||||
// entry in /dev printed as "0, 0" under ls -l -- harmless while nothing
|
||||
|
|
@ -301,6 +303,24 @@ linux_arg_mode(int n)
|
|||
return mode & 07777;
|
||||
}
|
||||
|
||||
// May the caller add or remove names in directory `dp`? It takes write and
|
||||
// search on the directory, and in a sticky one (/tmp) only the owner of the
|
||||
// file or of the directory may remove or rename a name -- `victim` is the
|
||||
// inode being removed, or 0 when a name is being added. -EACCES, or -EPERM for
|
||||
// the sticky rule, as Linux answers them.
|
||||
static int
|
||||
dir_may_modify(struct inode *dp, struct inode *victim)
|
||||
{
|
||||
int r = inode_permission(dp, 3, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (victim && (inode_mode_bits(dp) & 01000) && myproc() &&
|
||||
myproc()->euid != 0 && myproc()->euid != dp->uid &&
|
||||
myproc()->euid != victim->uid)
|
||||
return -EPERM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct inode*
|
||||
createat_x(int dirfd, char *path, short type, short major, short minor,
|
||||
int *err, int mode)
|
||||
|
|
@ -345,6 +365,11 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
|
|||
iunlockput(ip);
|
||||
return 0;
|
||||
}
|
||||
if (dir_may_modify(dp, 0) < 0) {
|
||||
iunlockput(dp);
|
||||
if (err) *err = EACCES;
|
||||
return 0;
|
||||
}
|
||||
// v1 and SchFS cannot fail either of these for a reason the caller could
|
||||
// have avoided. FAT can: the volume fills up, the fixed root directory of
|
||||
// FAT16 holds 512 names and no more, and a name may hold characters FAT
|
||||
|
|
@ -357,6 +382,11 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
|
|||
ilock(ip);
|
||||
ip->major = major; ip->minor = minor; ip->nlink = 1;
|
||||
ip->showmode = 1;
|
||||
// Owned by whoever made it. A directory with the setgid bit hands its
|
||||
// group on, which is how a shared directory keeps one group.
|
||||
ip->uid = myproc() ? myproc()->euid : 0;
|
||||
ip->gid = (inode_mode_bits(dp) & 02000) ? dp->gid
|
||||
: (myproc() ? myproc()->egid : 0);
|
||||
// The permission bits asked for, less the umask -- or, from a caller that
|
||||
// has none to give, what open() and mkdir() would ask for. A symlink's
|
||||
// are 0777 on Linux whatever the umask, and nothing reads them.
|
||||
|
|
@ -704,6 +734,20 @@ open_existing(int dirfd, char *path, int omode)
|
|||
return at_nameif(dirfd, path, &err);
|
||||
}
|
||||
|
||||
// May the caller open an existing file this way? Newly created files are not
|
||||
// asked: a file made with O_CREAT and mode 0444 can still be written by the
|
||||
// call that made it.
|
||||
static int
|
||||
open_perm(struct inode *ip, int omode)
|
||||
{
|
||||
int acc = omode & O_ACCMODE, mask = 0;
|
||||
if (acc == O_RDONLY || acc == O_RDWR)
|
||||
mask |= 4;
|
||||
if (acc == O_WRONLY || acc == O_RDWR || (omode & O_TRUNC))
|
||||
mask |= 2;
|
||||
return inode_permission(ip, mask, 0);
|
||||
}
|
||||
|
||||
addr_t sys_open(void)
|
||||
{
|
||||
char *path;
|
||||
|
|
@ -722,6 +766,7 @@ addr_t sys_open(void)
|
|||
if ((ip = open_existing(AT_FDCWD, path, omode)) != 0) {
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
if ((cerr = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return cerr; }
|
||||
} else
|
||||
ip = createat_x(AT_FDCWD, path,
|
||||
(omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr,
|
||||
|
|
@ -737,6 +782,7 @@ addr_t sys_open(void)
|
|||
int err;
|
||||
if (!(ip = open_lookup(AT_FDCWD, path, omode, &err))) { end_op(); return -err; }
|
||||
ilock(ip);
|
||||
if ((err = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return err; }
|
||||
// Directories may only be opened for reading, but callers (e.g.
|
||||
// opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/
|
||||
// O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter.
|
||||
|
|
@ -789,6 +835,7 @@ addr_t sys_openat(void)
|
|||
if ((ip = open_existing(dirfd, path, omode)) != 0) {
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
if ((cerr = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return cerr; }
|
||||
} else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr,
|
||||
linux_arg_mode(3)))) {
|
||||
end_op();
|
||||
|
|
@ -798,6 +845,7 @@ addr_t sys_openat(void)
|
|||
int err;
|
||||
if (!(ip = open_lookup(dirfd, path, omode, &err))) { end_op(); return -err; }
|
||||
ilock(ip);
|
||||
if ((err = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return err; }
|
||||
// Directories may only be opened for reading, but callers (e.g.
|
||||
// opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/
|
||||
// O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter.
|
||||
|
|
@ -922,16 +970,38 @@ addr_t sys_lstat(void)
|
|||
return do_stat(AT_FDCWD, path, st, 0);
|
||||
}
|
||||
|
||||
// access(2) and friends: may the caller (by real ids, unless AT_EACCESS) do
|
||||
// `amode` -- F_OK 0, X_OK 1, W_OK 2, R_OK 4 -- to this inode? Write on a
|
||||
// read-only filesystem is EROFS rather than EACCES.
|
||||
static int
|
||||
access_check(struct inode *ip, int amode, int real)
|
||||
{
|
||||
int r = 0;
|
||||
amode &= 7;
|
||||
if (amode == 0)
|
||||
return 0;
|
||||
ilock(ip);
|
||||
if ((amode & 2) && fs_readonly(ip->dev) && ip->type != T_DEV)
|
||||
r = -EROFS;
|
||||
else
|
||||
r = inode_permission(ip, amode, real);
|
||||
iunlock(ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
addr_t sys_access(void)
|
||||
{
|
||||
char *path;
|
||||
int amode = 0;
|
||||
if (argstr(0, &path) < 0) return -1;
|
||||
argint(1, &amode);
|
||||
int err;
|
||||
begin_op();
|
||||
struct inode *ip = at_nameif(AT_FDCWD, path, &err);
|
||||
if (!ip) { end_op(); return -err; }
|
||||
err = access_check(ip, amode, 1);
|
||||
iput(ip); end_op();
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
addr_t sys_chdir(void)
|
||||
|
|
@ -947,6 +1017,7 @@ addr_t sys_chdir(void)
|
|||
// shell reported "Operation not permitted" -- which reads like a
|
||||
// permissions problem and sends you looking in entirely the wrong place.
|
||||
if (ip->type != T_DIR) { iunlockput(ip); end_op(); return -ENOTDIR; }
|
||||
if ((err = inode_permission(ip, 1, 0)) < 0) { iunlockput(ip); end_op(); return err; }
|
||||
iunlock(ip); iput(myproc()->cwd); end_op();
|
||||
myproc()->cwd = ip;
|
||||
if (path[0] == '/') {
|
||||
|
|
@ -969,6 +1040,7 @@ addr_t sys_fchdir(void)
|
|||
if (argfd(0, 0, &f) < 0 || f->type != FD_INODE) return -1;
|
||||
ilock(f->ip);
|
||||
if (f->ip->type != T_DIR) { iunlock(f->ip); return -1; }
|
||||
if (inode_permission(f->ip, 1, 0) < 0) { iunlock(f->ip); return -EACCES; }
|
||||
iunlock(f->ip);
|
||||
iput(myproc()->cwd); idup(f->ip); myproc()->cwd = f->ip;
|
||||
myproc()->cwd_path[0] = '/'; myproc()->cwd_path[1] = 0;
|
||||
|
|
@ -1040,6 +1112,7 @@ do_mknod(int dirfd, char *path, uint32 mode, uint64 dev)
|
|||
int major = (int)(((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffull));
|
||||
int minor = (int)((dev & 0xff) | ((dev >> 12) & ~0xffull));
|
||||
if (type == T_FILE) major = minor = 0;
|
||||
if (type == T_DEV && !capable()) return -EPERM;
|
||||
begin_op();
|
||||
// createat_x() opens an existing file when asked for one, as open(O_CREAT)
|
||||
// wants. mknod() must not.
|
||||
|
|
@ -1108,6 +1181,7 @@ addr_t sys_link(void)
|
|||
ip->nlink++; iupdate(ip); iunlock(ip);
|
||||
if (!(dp = nameiparent(new, name))) { err = ENOENT; goto bad_link; }
|
||||
ilock(dp);
|
||||
if (dir_may_modify(dp, 0) < 0) { iunlockput(dp); err = EACCES; goto bad_link; }
|
||||
if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; }
|
||||
if (dp->ops->no_hardlinks) { iunlockput(dp); err = EPERM; goto bad_link; }
|
||||
if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; }
|
||||
|
|
@ -1171,6 +1245,8 @@ unlink_path(char *path)
|
|||
if (!(ip = dirlookup(dp, name, &off))) { err = ENOENT; goto bad; }
|
||||
ilock(ip);
|
||||
if (ip->nlink < 1) panic("unlink: nlink < 1");
|
||||
if ((err = -dir_may_modify(dp, ip)) != 0) { iunlockput(ip); goto bad; }
|
||||
err = EPERM;
|
||||
if (ip->type == T_DIR && dirempty(ip) != 1) { iunlockput(ip); err = ENOTEMPTY; goto bad; }
|
||||
if (dirunlink(dp, name, off) < 0) { iunlockput(ip); err = EIO; goto bad; }
|
||||
if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); }
|
||||
|
|
@ -1246,6 +1322,7 @@ addr_t sys_truncate(void)
|
|||
begin_op();
|
||||
if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; }
|
||||
ilock(ip);
|
||||
if ((err = inode_permission(ip, 2, 0)) < 0) { iunlockput(ip); end_op(); return err; }
|
||||
itrunc_to(ip, (uint64)length);
|
||||
inotify_event(ip->dev, ip->inum, IN_MODIFY, 0, 0);
|
||||
iunlockput(ip); end_op();
|
||||
|
|
@ -1306,8 +1383,22 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath)
|
|||
uint new_dev = dp_new->dev; // for inotify, after dp_new is put
|
||||
uint64 new_inum = dp_new->inum;
|
||||
|
||||
// Both directories are checked before anything changes: a refusal after
|
||||
// the new name is in place would leave the file with two.
|
||||
ilock(dp_old);
|
||||
int perm = dir_may_modify(dp_old, ip);
|
||||
iunlock(dp_old);
|
||||
if (perm < 0) {
|
||||
iput(dp_old); iput(dp_new);
|
||||
err = EACCES; goto bad_rename;
|
||||
}
|
||||
|
||||
// Clear the destination, if anything is there.
|
||||
ilock(dp_new);
|
||||
if (dir_may_modify(dp_new, 0) < 0) {
|
||||
iunlockput(dp_new); iput(dp_old);
|
||||
err = EACCES; goto bad_rename;
|
||||
}
|
||||
if ((ip_dst = dirlookup(dp_new, newname, &dstoff)) != 0) {
|
||||
// Both names already reach the same file. POSIX: succeed and do
|
||||
// nothing -- which includes not unlinking either name, since removing
|
||||
|
|
@ -1955,7 +2046,15 @@ chmod_inode(struct inode *ip, int mode)
|
|||
if (fs_readonly(ip->dev))
|
||||
return -EROFS;
|
||||
ilock(ip);
|
||||
// The owner may change the mode, and so may root. Setting the group id bit
|
||||
// on a file whose group you are not in is quietly dropped, as on Linux.
|
||||
if (myproc()->euid != 0 && myproc()->euid != ip->uid) {
|
||||
iunlock(ip);
|
||||
return -EPERM;
|
||||
}
|
||||
ip->mode = (uint)mode & 07777;
|
||||
if (myproc()->euid != 0 && !in_group(ip->gid, 0))
|
||||
ip->mode &= ~02000u;
|
||||
if (ip->mode == 0)
|
||||
ip->mode = 01000000; // "no bits", as distinct from "never set"
|
||||
iupdate(ip);
|
||||
|
|
@ -1991,9 +2090,89 @@ addr_t sys_fchmod(void)
|
|||
end_op();
|
||||
return r;
|
||||
}
|
||||
addr_t sys_chown(void) { return 0; }
|
||||
addr_t sys_fchown(void) { return 0; }
|
||||
addr_t sys_lchown(void) { return 0; }
|
||||
// chown(2): -1 leaves an id alone. Root may do anything. Anyone else may only
|
||||
// give a file they own to a group they are in, and any change by a non-root
|
||||
// caller drops the set-user-id and set-group-id bits, so that a program cannot
|
||||
// be handed over and stay privileged.
|
||||
static int
|
||||
chown_inode(struct inode *ip, int uid, int gid)
|
||||
{
|
||||
uint nu = (uint)uid, ng = (uint)gid;
|
||||
int root = myproc()->euid == 0;
|
||||
|
||||
if (fs_readonly(ip->dev))
|
||||
return -EROFS;
|
||||
ilock(ip);
|
||||
if (!root) {
|
||||
if (myproc()->euid != ip->uid ||
|
||||
(uid != -1 && nu != ip->uid) ||
|
||||
(gid != -1 && !in_group(ng, 0))) {
|
||||
iunlock(ip);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
if (uid != -1)
|
||||
ip->uid = nu;
|
||||
if (gid != -1)
|
||||
ip->gid = ng;
|
||||
if (!root || ip->type != T_DIR) {
|
||||
uint m = inode_mode_bits(ip);
|
||||
if (m & 06000 && (uid != -1 || gid != -1) && ip->type != T_DIR) {
|
||||
ip->mode = m & ~06000u;
|
||||
if (ip->mode == 0)
|
||||
ip->mode = 01000000;
|
||||
}
|
||||
}
|
||||
iupdate(ip);
|
||||
inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0);
|
||||
iunlock(ip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_chown(int dirfd, char *path, int uid, int gid, int nofollow)
|
||||
{
|
||||
struct inode *ip;
|
||||
int err, r;
|
||||
begin_op();
|
||||
ip = nofollow ? at_namei(dirfd, path) : at_nameif(dirfd, path, &err);
|
||||
if (!ip) { end_op(); return nofollow ? -ENOENT : -err; }
|
||||
r = chown_inode(ip, uid, gid);
|
||||
iput(ip);
|
||||
end_op();
|
||||
return r;
|
||||
}
|
||||
|
||||
addr_t sys_chown(void)
|
||||
{
|
||||
char *path;
|
||||
int uid, gid;
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
argint(1, &uid); argint(2, &gid);
|
||||
return do_chown(AT_FDCWD, path, uid, gid, 0);
|
||||
}
|
||||
|
||||
addr_t sys_lchown(void)
|
||||
{
|
||||
char *path;
|
||||
int uid, gid;
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
argint(1, &uid); argint(2, &gid);
|
||||
return do_chown(AT_FDCWD, path, uid, gid, 1);
|
||||
}
|
||||
|
||||
addr_t sys_fchown(void)
|
||||
{
|
||||
struct file *f;
|
||||
int uid, gid, r;
|
||||
if (argfd(0, 0, &f) < 0) return -EBADF;
|
||||
argint(1, &uid); argint(2, &gid);
|
||||
if (f->type != FD_INODE || !f->ip) return 0;
|
||||
begin_op();
|
||||
r = chown_inode(f->ip, uid, gid);
|
||||
end_op();
|
||||
return r;
|
||||
}
|
||||
|
||||
// rmdir: remove an empty directory (same as unlink for dirs).
|
||||
addr_t sys_rmdir(void)
|
||||
|
|
@ -2118,6 +2297,16 @@ addr_t sys_utimensat(void)
|
|||
begin_op();
|
||||
ilock(ip);
|
||||
if (fs_readonly(ip->dev)) { iunlockput(ip); end_op(); return -EROFS; }
|
||||
// Setting the times to "now" takes write access or ownership; setting them
|
||||
// to anything else takes ownership.
|
||||
if (myproc()->euid != 0 && myproc()->euid != ip->uid) {
|
||||
int explicit_t = 0;
|
||||
for (int i = 0; utimes && i < 2; i++)
|
||||
if (ts[i].tv_nsec != UTIME_OMIT && ts[i].tv_nsec != UTIME_NOW)
|
||||
explicit_t = 1;
|
||||
int pr = explicit_t ? -EPERM : inode_permission(ip, 2, 0);
|
||||
if (pr < 0) { iunlockput(ip); end_op(); return pr; }
|
||||
}
|
||||
for (int i = 0; i < 2; i++) {
|
||||
long sec = utimes ? ts[i].tv_sec : (long)now;
|
||||
if (utimes && ts[i].tv_nsec == UTIME_OMIT) continue;
|
||||
|
|
@ -2238,6 +2427,7 @@ addr_t sys_chroot(void)
|
|||
struct inode *ip;
|
||||
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
if (!capable()) return -EPERM;
|
||||
begin_op();
|
||||
if ((ip = namei(path)) == 0) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
|
|
@ -2416,7 +2606,15 @@ addr_t sys_fchmodat(void)
|
|||
end_op();
|
||||
return r;
|
||||
}
|
||||
addr_t sys_fchownat(void) { return 0; } // stub
|
||||
addr_t sys_fchownat(void)
|
||||
{
|
||||
int dirfd, uid, gid, flags;
|
||||
char *path;
|
||||
argint(0, &dirfd);
|
||||
if (argstr(1, &path) < 0) return -EFAULT;
|
||||
argint(2, &uid); argint(3, &gid); argint(4, &flags);
|
||||
return do_chown(dirfd, path, uid, gid, flags & AT_SYMLINK_NOFOLLOW);
|
||||
}
|
||||
|
||||
addr_t sys_faccessat(void)
|
||||
{
|
||||
|
|
@ -2424,14 +2622,15 @@ addr_t sys_faccessat(void)
|
|||
char *path;
|
||||
argint(0, &dirfd);
|
||||
if (argstr(1, &path) < 0) return -1;
|
||||
argint(2, &(int){0}); // amode — ignored
|
||||
// arg3 flags — ignored
|
||||
int amode = 0;
|
||||
argint(2, &amode);
|
||||
int err;
|
||||
begin_op();
|
||||
struct inode *ip = at_nameif(dirfd, path, &err);
|
||||
if (!ip) { end_op(); return -err; }
|
||||
err = access_check(ip, amode, 1);
|
||||
iput(ip); end_op();
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
// faccessat2: faccessat with the flags argument the original never had. musl
|
||||
|
|
@ -2460,9 +2659,10 @@ addr_t sys_faccessat2(void)
|
|||
ip = (flags & AT_SYMLINK_NOFOLLOW) ? at_namei(dirfd, path)
|
||||
: at_nameif(dirfd, path, &err);
|
||||
if (!ip) { end_op(); return -err; }
|
||||
err = access_check(ip, mode, !(flags & AT_EACCESS));
|
||||
iput(ip);
|
||||
end_op();
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
addr_t sys_fstatat(void)
|
||||
|
|
@ -2556,6 +2756,8 @@ addr_t sys_mount(void)
|
|||
if (argstr(0, &source) < 0 || argstr(1, &target) < 0 ||
|
||||
argstr(2, &fstype) < 0)
|
||||
return -EFAULT;
|
||||
if (!capable())
|
||||
return -EPERM;
|
||||
|
||||
// proc: a synthetic filesystem, so there is no source device to check and
|
||||
// no volume to have found at boot. `mount -t proc proc /proc`.
|
||||
|
|
@ -2697,6 +2899,8 @@ addr_t sys_umount2(void)
|
|||
{
|
||||
char *target;
|
||||
if (argstr(0, &target) < 0) return -EFAULT;
|
||||
if (!capable())
|
||||
return -EPERM;
|
||||
|
||||
begin_op();
|
||||
struct inode *dp = namei(target);
|
||||
|
|
|
|||
|
|
@ -59,18 +59,27 @@ sys_kill(void)
|
|||
// pid == 0 -> every process in the caller's own group
|
||||
// pid == -1 -> every process we may signal (approximated as our group)
|
||||
// pid < -1 -> the process group with id -pid
|
||||
if (pid == 0 || pid == -1)
|
||||
return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0;
|
||||
if (pid < -1)
|
||||
return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0;
|
||||
|
||||
{
|
||||
int r = 0;
|
||||
if (pid == 0 || pid == -1)
|
||||
r = sigsend_pgrp_user(myproc()->pgid, sig);
|
||||
else if (pid < -1)
|
||||
r = sigsend_pgrp_user(-pid, sig);
|
||||
else
|
||||
goto single;
|
||||
return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0;
|
||||
}
|
||||
single:
|
||||
// sig == 0: existence check only
|
||||
if (sig == 0)
|
||||
return proc_exists(pid) ? 0 : -ESRCH;
|
||||
|
||||
// Raise the signal for real: pending bit + wakeup, handler delivery on
|
||||
// the target's next syscall boundary, default action if unhandled.
|
||||
return sigsend(pid, sig) < 0 ? -ESRCH : 0;
|
||||
{
|
||||
int r = sigsend_user(pid, sig);
|
||||
return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Defined with getrusage() further down; wait4() needs it too.
|
||||
|
|
@ -208,6 +217,8 @@ sys_uptime(void)
|
|||
|
||||
addr_t sys_reboot(void)
|
||||
{
|
||||
if (!capable())
|
||||
return -EPERM;
|
||||
cprintf("System is rebooting...\n");
|
||||
// A stick left mounted should come back readable, and marked clean.
|
||||
schfs_ksync_all();
|
||||
|
|
@ -223,6 +234,8 @@ addr_t sys_reboot(void)
|
|||
|
||||
addr_t sys_shutdown(void)
|
||||
{
|
||||
if (!capable())
|
||||
return -EPERM;
|
||||
cprintf("System is shutting down...\n");
|
||||
// S5, through the firmware's own PM1 registers and its _PTS. The port
|
||||
// writes after it are QEMU's, Bochs' and VirtualBox's fixed addresses,
|
||||
|
|
@ -335,12 +348,19 @@ addr_t
|
|||
sys_setreuid(void)
|
||||
{
|
||||
int ruid, euid;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &ruid); argint(1, &euid);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (ruid >= 0) myproc()->uid = (uint)ruid;
|
||||
if (euid >= 0) myproc()->euid = (uint)euid;
|
||||
myproc()->suid = myproc()->euid;
|
||||
if (p->euid != 0) {
|
||||
if (ruid >= 0 && (uint)ruid != p->uid && (uint)ruid != p->euid)
|
||||
return -EPERM;
|
||||
if (euid >= 0 && (uint)euid != p->uid && (uint)euid != p->euid &&
|
||||
(uint)euid != p->suid)
|
||||
return -EPERM;
|
||||
}
|
||||
if (ruid >= 0 || (euid >= 0 && (uint)euid != p->uid))
|
||||
p->suid = euid >= 0 ? (uint)euid : p->euid;
|
||||
if (ruid >= 0) p->uid = (uint)ruid;
|
||||
if (euid >= 0) p->euid = (uint)euid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -348,12 +368,19 @@ addr_t
|
|||
sys_setregid(void)
|
||||
{
|
||||
int rgid, egid;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &rgid); argint(1, &egid);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (rgid >= 0) myproc()->gid = (uint)rgid;
|
||||
if (egid >= 0) myproc()->egid = (uint)egid;
|
||||
myproc()->sgid = myproc()->egid;
|
||||
if (p->euid != 0) {
|
||||
if (rgid >= 0 && (uint)rgid != p->gid && (uint)rgid != p->egid)
|
||||
return -EPERM;
|
||||
if (egid >= 0 && (uint)egid != p->gid && (uint)egid != p->egid &&
|
||||
(uint)egid != p->sgid)
|
||||
return -EPERM;
|
||||
}
|
||||
if (rgid >= 0 || (egid >= 0 && (uint)egid != p->gid))
|
||||
p->sgid = egid >= 0 ? (uint)egid : p->egid;
|
||||
if (rgid >= 0) p->gid = (uint)rgid;
|
||||
if (egid >= 0) p->egid = (uint)egid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -361,12 +388,19 @@ addr_t
|
|||
sys_setresuid(void)
|
||||
{
|
||||
int r, e, sv;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &r); argint(1, &e); argint(2, &sv);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (r >= 0) myproc()->uid = (uint)r;
|
||||
if (e >= 0) myproc()->euid = (uint)e;
|
||||
if (sv >= 0) myproc()->suid = (uint)sv;
|
||||
// Unprivileged, each id may be set only to one the process already has.
|
||||
if (p->euid != 0) {
|
||||
int ids[3] = { r, e, sv };
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (ids[i] >= 0 && (uint)ids[i] != p->uid &&
|
||||
(uint)ids[i] != p->euid && (uint)ids[i] != p->suid)
|
||||
return -EPERM;
|
||||
}
|
||||
if (r >= 0) p->uid = (uint)r;
|
||||
if (e >= 0) p->euid = (uint)e;
|
||||
if (sv >= 0) p->suid = (uint)sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -374,12 +408,18 @@ addr_t
|
|||
sys_setresgid(void)
|
||||
{
|
||||
int r, e, sv;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &r); argint(1, &e); argint(2, &sv);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (r >= 0) myproc()->gid = (uint)r;
|
||||
if (e >= 0) myproc()->egid = (uint)e;
|
||||
if (sv >= 0) myproc()->sgid = (uint)sv;
|
||||
if (p->euid != 0) {
|
||||
int ids[3] = { r, e, sv };
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (ids[i] >= 0 && (uint)ids[i] != p->gid &&
|
||||
(uint)ids[i] != p->egid && (uint)ids[i] != p->sgid)
|
||||
return -EPERM;
|
||||
}
|
||||
if (r >= 0) p->gid = (uint)r;
|
||||
if (e >= 0) p->egid = (uint)e;
|
||||
if (sv >= 0) p->sgid = (uint)sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -412,10 +452,39 @@ sys_getresgid(void)
|
|||
addr_t sys_getgroups(void)
|
||||
{
|
||||
int size;
|
||||
addr_t list;
|
||||
struct proc *p = myproc();
|
||||
argint(0, &size);
|
||||
return 0; // no supplementary groups
|
||||
argaddr(1, &list);
|
||||
if (size < 0)
|
||||
return -EINVAL;
|
||||
if (size == 0)
|
||||
return p->ngroups; // how many there are
|
||||
if (size < p->ngroups)
|
||||
return -EINVAL;
|
||||
if (p->ngroups && copy_to_user(list, p->groups, p->ngroups * sizeof(uint)) < 0)
|
||||
return -EFAULT;
|
||||
return p->ngroups;
|
||||
}
|
||||
|
||||
addr_t sys_setgroups(void)
|
||||
{
|
||||
int size;
|
||||
addr_t list;
|
||||
uint tmp[NGROUPS_MAX];
|
||||
struct proc *p = myproc();
|
||||
argint(0, &size);
|
||||
argaddr(1, &list);
|
||||
if (p->euid != 0)
|
||||
return -EPERM;
|
||||
if (size < 0 || size > NGROUPS_MAX)
|
||||
return -EINVAL;
|
||||
if (size && copy_from_user(tmp, list, size * sizeof(uint)) < 0)
|
||||
return -EFAULT;
|
||||
p->ngroups = size;
|
||||
memmove(p->groups, tmp, size * sizeof(uint));
|
||||
return 0;
|
||||
}
|
||||
addr_t sys_setgroups(void) { return 0; }
|
||||
|
||||
// --- Process/scheduling extensions ---
|
||||
|
||||
|
|
@ -478,7 +547,10 @@ addr_t sys_tgkill(void)
|
|||
// the calling thread.
|
||||
if (tid <= 0) return -EINVAL;
|
||||
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
||||
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
||||
{
|
||||
int r = sigsend_user(tid, sig);
|
||||
return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0;
|
||||
}
|
||||
}
|
||||
|
||||
addr_t sys_tkill(void)
|
||||
|
|
@ -487,7 +559,10 @@ addr_t sys_tkill(void)
|
|||
argint(0, &tid); argint(1, &sig);
|
||||
if (tid <= 0) return 0;
|
||||
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
||||
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
||||
{
|
||||
int r = sigsend_user(tid, sig);
|
||||
return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0;
|
||||
}
|
||||
}
|
||||
|
||||
addr_t sys_pause(void)
|
||||
|
|
|
|||
Loading…
Reference in a new issue