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:
Nexoniarz 2026-10-26 13:16:07 +01:00
commit 020aa6113c
34 changed files with 1342 additions and 120 deletions

View file

@ -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);

View file

@ -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)