NetSurf: what GTK's images and libcurl's resolver needed
NetSurf (GTK 3) now starts under X and loads pages over HTTPS. Every icon GTK draws goes gdk-pixbuf -> glycin -> a loader process -> a sealed memfd -> GMappedFile, and each hop found something missing: - mkaroot runs Alpine's package triggers in the root under bubblewrap. --no-scripts had skipped them, so there was no MIME cache, pixbuf loader list, compiled GSettings schemas, GIO module cache, font cache or busybox applet links. - /usr/bin/bwrap is a stand-in that parses bubblewrap's options and runs the program with no sandbox: glycin starts its loaders only through bwrap, and real bwrap needs namespaces. schos-base replaces bubblewrap for it. - ioctl: FIOCLEX, FIONCLEX, FIONBIO and FIOASYNC on any descriptor (Rust's std uses them; pipes and sockets answered ENOTTY). - capget/capset: root has every capability, others none. - socketpair accepts SOCK_DGRAM and SOCK_SEQPACKET, carried as a stream. - waitpid and setpgid match children against the thread group leader, so a thread can wait for the process it spawned (ECHILD before). - memfd: fstat reports the ftruncate length, not the page-rounded one; file seals (F_ADD_SEALS, F_GET_SEALS, MFD_ALLOW_SEALING) are recorded and enforced on later writes, truncation and writable mappings; MFD_CLOEXEC is honoured; read-only MAP_PRIVATE maps are allowed. - recvfrom honours O_NONBLOCK and MSG_DONTWAIT. musl's resolver reads its socket until EAGAIN and blocked for ever after the first DNS answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
f4d7f9100d
commit
393bf674f5
11 changed files with 251 additions and 21 deletions
|
|
@ -797,6 +797,7 @@ void shm_ref(int id);
|
||||||
char* shm_page(int id, int pg);
|
char* shm_page(int id, int pg);
|
||||||
uint64 memfd_size(int id);
|
uint64 memfd_size(int id);
|
||||||
int memfd_truncate(int id, uint64 len);
|
int memfd_truncate(int id, uint64 len);
|
||||||
|
int memfd_seals(int id, int add);
|
||||||
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);
|
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);
|
||||||
void shm_detach(int id);
|
void shm_detach(int id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,5 +46,15 @@
|
||||||
#define F_OFD_GETLK 36
|
#define F_OFD_GETLK 36
|
||||||
#define F_OFD_SETLK 37
|
#define F_OFD_SETLK 37
|
||||||
#define F_OFD_SETLKW 38
|
#define F_OFD_SETLKW 38
|
||||||
|
// memfd seals (Linux values).
|
||||||
|
#define F_ADD_SEALS 1033
|
||||||
|
#define F_GET_SEALS 1034
|
||||||
|
#define F_SEAL_SEAL 0x01
|
||||||
|
#define F_SEAL_SHRINK 0x02
|
||||||
|
#define F_SEAL_GROW 0x04
|
||||||
|
#define F_SEAL_WRITE 0x08
|
||||||
|
#define F_SEAL_FUTURE_WRITE 0x10
|
||||||
|
#define MFD_CLOEXEC 0x01
|
||||||
|
#define MFD_ALLOW_SEALING 0x02
|
||||||
|
|
||||||
#define FD_CLOEXEC 1
|
#define FD_CLOEXEC 1
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,7 @@ int alarm(int seconds);
|
||||||
int waitpid(int pid, int *status, int options);
|
int waitpid(int pid, int *status, int options);
|
||||||
int setsid(void);
|
int setsid(void);
|
||||||
void* mmap(void*, uint64, int, int, int, uint64);
|
void* mmap(void*, uint64, int, int, int, uint64);
|
||||||
// memfd_create(2). The flags argument exists for MFD_CLOEXEC and MFD_ALLOW_
|
// memfd_create(2). flags: MFD_CLOEXEC, MFD_ALLOW_SEALING.
|
||||||
// SEALING, neither of which this kernel implements; pass 0.
|
|
||||||
int memfd_create(const char *name, uint flags);
|
int memfd_create(const char *name, uint flags);
|
||||||
// Shared memory. shmat returns the attach address, or a small negative value
|
// Shared memory. shmat returns the attach address, or a small negative value
|
||||||
// on failure — there is no errno in this runtime, so check for < 0.
|
// on failure — there is no errno in this runtime, so check for < 0.
|
||||||
|
|
|
||||||
|
|
@ -1477,18 +1477,21 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st)
|
||||||
{
|
{
|
||||||
struct proc *p;
|
struct proc *p;
|
||||||
int havekids;
|
int havekids;
|
||||||
|
// Children belong to the process, not to the thread that made them: any
|
||||||
|
// thread may wait for them, and their exit wakes the leader's channel.
|
||||||
|
struct proc *me = proc_leader(myproc());
|
||||||
|
|
||||||
acquire(&ptable.lock);
|
acquire(&ptable.lock);
|
||||||
for(;;){
|
for(;;){
|
||||||
havekids = 0;
|
havekids = 0;
|
||||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||||
if(p->parent != myproc())
|
if(p->parent != me)
|
||||||
continue;
|
continue;
|
||||||
// POSIX pid selection: >0 one child, 0 = caller's group,
|
// POSIX pid selection: >0 one child, 0 = caller's group,
|
||||||
// -1 = any child, < -1 = the process group with id -pid.
|
// -1 = any child, < -1 = the process group with id -pid.
|
||||||
if(pid > 0 && p->pid != pid)
|
if(pid > 0 && p->pid != pid)
|
||||||
continue;
|
continue;
|
||||||
if(pid == 0 && p->pgid != myproc()->pgid)
|
if(pid == 0 && p->pgid != me->pgid)
|
||||||
continue;
|
continue;
|
||||||
if(pid < -1 && p->pgid != -pid)
|
if(pid < -1 && p->pgid != -pid)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1503,8 +1506,8 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st)
|
||||||
// RUSAGE_CHILDREN: the child's own time *and* whatever it had already
|
// RUSAGE_CHILDREN: the child's own time *and* whatever it had already
|
||||||
// reaped, which is what makes the total transitive down a whole tree
|
// reaped, which is what makes the total transitive down a whole tree
|
||||||
// of processes rather than one generation deep.
|
// of processes rather than one generation deep.
|
||||||
myproc()->cutime += p->utime + p->cutime;
|
me->cutime += p->utime + p->cutime;
|
||||||
myproc()->cstime += p->stime + p->cstime;
|
me->cstime += p->stime + p->cstime;
|
||||||
reaped_utime += p->utime;
|
reaped_utime += p->utime;
|
||||||
reaped_stime += p->stime;
|
reaped_stime += p->stime;
|
||||||
// wait4()'s fourth argument wants the *child's* usage, not the
|
// wait4()'s fourth argument wants the *child's* usage, not the
|
||||||
|
|
@ -1576,7 +1579,7 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(myproc(), &ptable.lock);
|
sleep(me, &ptable.lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2699,7 +2702,7 @@ proc_setpgid(int pid, int pgid)
|
||||||
continue;
|
continue;
|
||||||
// A caller may only move itself or one of its own children, and never
|
// A caller may only move itself or one of its own children, and never
|
||||||
// across a session boundary.
|
// across a session boundary.
|
||||||
if(p != myproc() && p->parent != myproc())
|
if(p != myproc() && p->parent != proc_leader(myproc()))
|
||||||
r = -ESRCH;
|
r = -ESRCH;
|
||||||
else if(p->sid != myproc()->sid)
|
else if(p->sid != myproc()->sid)
|
||||||
r = -EPERM;
|
r = -EPERM;
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,13 @@ sys_fcntl(void)
|
||||||
case F_SETFL:
|
case F_SETFL:
|
||||||
f->nonblock = (arg & O_NONBLOCK) != 0;
|
f->nonblock = (arg & O_NONBLOCK) != 0;
|
||||||
return 0;
|
return 0;
|
||||||
|
case F_ADD_SEALS:
|
||||||
|
case F_GET_SEALS:
|
||||||
|
if (f->type != FD_MEMFD)
|
||||||
|
return -EINVAL;
|
||||||
|
if (cmd == F_ADD_SEALS && !f->writable)
|
||||||
|
return -EPERM;
|
||||||
|
return memfd_seals(f->memfd_seg, cmd == F_ADD_SEALS ? arg : -1);
|
||||||
case F_GETLK: case F_SETLK: case F_SETLKW:
|
case F_GETLK: case F_SETLK: case F_SETLKW:
|
||||||
case F_OFD_GETLK: case F_OFD_SETLK: case F_OFD_SETLKW: {
|
case F_OFD_GETLK: case F_OFD_SETLK: case F_OFD_SETLKW: {
|
||||||
// A pointer, which argint() above would have cut to 32 bits.
|
// A pointer, which argint() above would have cut to 32 bits.
|
||||||
|
|
@ -1547,6 +1554,28 @@ addr_t sys_ioctl(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The four that apply to any descriptor at all. Rust's standard library
|
||||||
|
// sets close-on-exec with FIOCLEX and non-blocking with FIONBIO rather
|
||||||
|
// than with fcntl, and on a pipe or a socket those fell through to the
|
||||||
|
// ENOTTY below: glycin's image loaders failed with "Not a tty" and GTK
|
||||||
|
// could load no icon.
|
||||||
|
if (request == 0x5451 || request == 0x5450) { // FIOCLEX, FIONCLEX
|
||||||
|
if (request == 0x5451)
|
||||||
|
myproc()->files->flags[fd] |= FD_CLOEXEC;
|
||||||
|
else
|
||||||
|
myproc()->files->flags[fd] &= ~FD_CLOEXEC;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (request == 0x5421) { // FIONBIO
|
||||||
|
int on;
|
||||||
|
if (copy_from_user(&on, argp, sizeof(on)) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
f->nonblock = on != 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (request == 0x5452) // FIOASYNC
|
||||||
|
return 0;
|
||||||
|
|
||||||
// /dev/fb0 (major 2) — geometry queries. This has to come before the
|
// /dev/fb0 (major 2) — geometry queries. This has to come before the
|
||||||
// catch-all below, which answers every non-tty ioctl with 0: a program
|
// catch-all below, which answers every non-tty ioctl with 0: a program
|
||||||
// asking FBIOGET_VSCREENINFO would have been told "success" and then
|
// asking FBIOGET_VSCREENINFO would have been told "success" and then
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "gfx/gfx.h"
|
#include "gfx/gfx.h"
|
||||||
#include "core/param.h"
|
#include "core/param.h"
|
||||||
#include "uapi/stat.h"
|
#include "uapi/stat.h"
|
||||||
|
#include "uapi/fcntl.h"
|
||||||
#include "arch/memlayout.h"
|
#include "arch/memlayout.h"
|
||||||
#include "arch/mmu.h"
|
#include "arch/mmu.h"
|
||||||
#include "core/spinlock.h"
|
#include "core/spinlock.h"
|
||||||
|
|
@ -272,15 +273,21 @@ mmap_locked(void)
|
||||||
// process that maps the same segment gets the same physical pages, and a
|
// process that maps the same segment gets the same physical pages, and a
|
||||||
// store by one is a load by the other because there is only one copy.
|
// store by one is a load by the other because there is only one copy.
|
||||||
//
|
//
|
||||||
// MAP_PRIVATE is refused rather than given a copy. Every caller of this is
|
// A private mapping is allowed read-only, and is then the same pages
|
||||||
// sharing on purpose, and a private mapping of an anonymous object is an
|
// mapped without write access: until somebody writes, a private mapping
|
||||||
// expensive way to ask for MAP_ANONYMOUS.
|
// sees the object's stores on Linux too. GLib's GMappedFile maps a memfd
|
||||||
|
// this way, and that is how gdk-pixbuf reads every image glycin decodes.
|
||||||
|
// Private and writable would need a copy that is not made; it is refused.
|
||||||
if (f && f->type == FD_MEMFD) {
|
if (f && f->type == FD_MEMFD) {
|
||||||
int id = f->memfd_seg;
|
int id = f->memfd_seg;
|
||||||
uint64 have = memfd_size(id);
|
uint64 have = PGROUNDUP(memfd_size(id)); // the tail page maps whole
|
||||||
|
|
||||||
if (!(flags & MAP_SHARED))
|
if (!(flags & MAP_SHARED) && (prot & PROT_WRITE))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
int pte = (flags & MAP_SHARED) ? PTE_W | PTE_U : PTE_U;
|
||||||
|
if ((prot & PROT_WRITE) &&
|
||||||
|
(memfd_seals(id, -1) & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)))
|
||||||
|
return -EPERM;
|
||||||
if (offset % PGSIZE)
|
if (offset % PGSIZE)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
// Mapping past the end is not an error on Linux -- the pages simply
|
// Mapping past the end is not an error on Linux -- the pages simply
|
||||||
|
|
@ -295,7 +302,7 @@ mmap_locked(void)
|
||||||
for (uint64 o = 0; o < length; o += PGSIZE) {
|
for (uint64 o = 0; o < length; o += PGSIZE) {
|
||||||
char *pg = shm_page(id, (int)((offset + o) / PGSIZE));
|
char *pg = shm_page(id, (int)((offset + o) / PGSIZE));
|
||||||
if (!pg || mappages(myproc()->pgdir, (void*)(va + o), PGSIZE,
|
if (!pg || mappages(myproc()->pgdir, (void*)(va + o), PGSIZE,
|
||||||
V2P((uint64)pg), PTE_W | PTE_U) < 0) {
|
V2P((uint64)pg), pte) < 0) {
|
||||||
vm_unmap_norelease(myproc()->pgdir, va, o);
|
vm_unmap_norelease(myproc()->pgdir, va, o);
|
||||||
shm_detach(id);
|
shm_detach(id);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
@ -517,6 +524,8 @@ struct shmseg {
|
||||||
int used;
|
int used;
|
||||||
int key;
|
int key;
|
||||||
uint64 size; // rounded up to a page
|
uint64 size; // rounded up to a page
|
||||||
|
uint64 bytes; // a memfd's length as ftruncate gave it
|
||||||
|
int seals; // a memfd's F_SEAL_* bits
|
||||||
int npages;
|
int npages;
|
||||||
int nattach; // live attachments across all processes
|
int nattach; // live attachments across all processes
|
||||||
char *pages[SHM_MAXPAGES]; // kernel virtual addresses
|
char *pages[SHM_MAXPAGES]; // kernel virtual addresses
|
||||||
|
|
@ -605,6 +614,7 @@ shm_resize(int id, uint64 newsize)
|
||||||
{
|
{
|
||||||
struct shmseg *sg;
|
struct shmseg *sg;
|
||||||
int want, i;
|
int want, i;
|
||||||
|
uint64 len = newsize;
|
||||||
|
|
||||||
if (id < 0 || id >= NSHM)
|
if (id < 0 || id >= NSHM)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
@ -644,6 +654,7 @@ shm_resize(int id, uint64 newsize)
|
||||||
acquire(&shmlock);
|
acquire(&shmlock);
|
||||||
sg->npages = want;
|
sg->npages = want;
|
||||||
sg->size = newsize;
|
sg->size = newsize;
|
||||||
|
sg->bytes = len;
|
||||||
release(&shmlock);
|
release(&shmlock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -671,7 +682,11 @@ memfd_size(int id)
|
||||||
return 0;
|
return 0;
|
||||||
shm_init_once();
|
shm_init_once();
|
||||||
acquire(&shmlock);
|
acquire(&shmlock);
|
||||||
n = shm[id].used ? shm[id].size : 0;
|
// The length asked for, not the pages behind it. fstat reports this, and
|
||||||
|
// memmap2 -- which glycin uses to share every decoded image -- sizes its
|
||||||
|
// mapping by it: 24x24 icons came back as 4096 bytes instead of 2304, and
|
||||||
|
// the PNG decoder asserted.
|
||||||
|
n = shm[id].used ? shm[id].bytes : 0;
|
||||||
release(&shmlock);
|
release(&shmlock);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
@ -679,9 +694,47 @@ memfd_size(int id)
|
||||||
int
|
int
|
||||||
memfd_truncate(int id, uint64 len)
|
memfd_truncate(int id, uint64 len)
|
||||||
{
|
{
|
||||||
|
if (id < 0 || id >= NSHM)
|
||||||
|
return -EINVAL;
|
||||||
|
uint64 now = memfd_size(id);
|
||||||
|
if ((len < now && (shm[id].seals & F_SEAL_SHRINK)) ||
|
||||||
|
(len > now && (shm[id].seals & F_SEAL_GROW)))
|
||||||
|
return -EPERM;
|
||||||
return shm_resize(id, len);
|
return shm_resize(id, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File seals: promises about a memfd that whoever receives it can rely on.
|
||||||
|
// glycin seals each decoded image before handing it over, and gdk-pixbuf
|
||||||
|
// first asks for the seals just to learn the descriptor is a memfd at all.
|
||||||
|
//
|
||||||
|
// Linux refuses F_SEAL_WRITE while a writable mapping exists (EBUSY). That
|
||||||
|
// needs every mapping of the segment to be made read-only on the spot, which
|
||||||
|
// takes the page fault handling this kernel does not have, so the seal is
|
||||||
|
// recorded and later writes, truncates and writable mappings are refused --
|
||||||
|
// the mapping that already exists keeps writing.
|
||||||
|
int
|
||||||
|
memfd_seals(int id, int add)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
if (id < 0 || id >= NSHM)
|
||||||
|
return -EINVAL;
|
||||||
|
acquire(&shmlock);
|
||||||
|
if (!shm[id].used)
|
||||||
|
r = -EINVAL;
|
||||||
|
else if (add < 0)
|
||||||
|
r = shm[id].seals;
|
||||||
|
else if (shm[id].seals & F_SEAL_SEAL)
|
||||||
|
r = -EPERM;
|
||||||
|
else if (add & ~(F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_FUTURE_WRITE))
|
||||||
|
r = -EINVAL;
|
||||||
|
else {
|
||||||
|
shm[id].seals |= add;
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
release(&shmlock);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
// read()/write() on a memfd, so it is a file rather than a token that only
|
// read()/write() on a memfd, so it is a file rather than a token that only
|
||||||
// mmap understands. Wayland clients use ftruncate and mmap, but a descriptor
|
// mmap understands. Wayland clients use ftruncate and mmap, but a descriptor
|
||||||
// that refuses read() is a surprise nobody needs.
|
// that refuses read() is a surprise nobody needs.
|
||||||
|
|
@ -697,10 +750,12 @@ memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing)
|
||||||
sg = &shm[id];
|
sg = &shm[id];
|
||||||
if (!sg->used)
|
if (!sg->used)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (off >= sg->size)
|
if (writing && (sg->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)))
|
||||||
|
return -EPERM;
|
||||||
|
if (off >= sg->bytes)
|
||||||
return 0; // EOF, or a write past the end
|
return 0; // EOF, or a write past the end
|
||||||
if (off + (uint64)n > sg->size)
|
if (off + (uint64)n > sg->bytes)
|
||||||
n = (int)(sg->size - off);
|
n = (int)(sg->bytes - off);
|
||||||
|
|
||||||
while (done < n) {
|
while (done < n) {
|
||||||
uint64 pos = off + (uint64)done;
|
uint64 pos = off + (uint64)done;
|
||||||
|
|
@ -751,6 +806,8 @@ sys_memfd_create(void)
|
||||||
shm[i].size = 0;
|
shm[i].size = 0;
|
||||||
shm[i].npages = 0;
|
shm[i].npages = 0;
|
||||||
shm[i].nattach = 1; // the file holds this one
|
shm[i].nattach = 1; // the file holds this one
|
||||||
|
// Without MFD_ALLOW_SEALING the object is sealed against sealing.
|
||||||
|
shm[i].seals = (flags & MFD_ALLOW_SEALING) ? 0 : F_SEAL_SEAL;
|
||||||
id = i;
|
id = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -761,6 +818,8 @@ sys_memfd_create(void)
|
||||||
if ((f = filealloc()) == 0) { shm_detach(id); return -ENFILE; }
|
if ((f = filealloc()) == 0) { shm_detach(id); return -ENFILE; }
|
||||||
if ((fd = fdalloc(f)) < 0) { fileclose(f); return -EMFILE; }
|
if ((fd = fdalloc(f)) < 0) { fileclose(f); return -EMFILE; }
|
||||||
|
|
||||||
|
if (flags & MFD_CLOEXEC)
|
||||||
|
myproc()->files->flags[fd] = FD_CLOEXEC;
|
||||||
f->type = FD_MEMFD;
|
f->type = FD_MEMFD;
|
||||||
f->memfd_seg = id;
|
f->memfd_seg = id;
|
||||||
f->readable = 1;
|
f->readable = 1;
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,12 @@ SYSCALL_DEFINE(socketpair)
|
||||||
return -EAFNOSUPPORT;
|
return -EAFNOSUPPORT;
|
||||||
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
|
||||||
type &= 0xFF;
|
type &= 0xFF;
|
||||||
if (type != SOCK_STREAM)
|
// Datagram and sequenced-packet pairs are carried as a stream. What they
|
||||||
|
// are used for between two processes is passing a descriptor or a status
|
||||||
|
// word -- Rust's process spawning asks for SOCK_SEQPACKET to hear back
|
||||||
|
// from its child -- and one message at a time is all of that. A reader
|
||||||
|
// that sends two and wants them kept apart would see them joined.
|
||||||
|
if (type != SOCK_STREAM && type != 2 /* DGRAM */ && type != 5 /* SEQPACKET */)
|
||||||
return -EPROTOTYPE;
|
return -EPROTOTYPE;
|
||||||
|
|
||||||
if ((r = sockpair(&a, &b)) < 0)
|
if ((r = sockpair(&a, &b)) < 0)
|
||||||
|
|
@ -532,15 +537,18 @@ SYSCALL_DEFINE(sendto)
|
||||||
return socksendto(s, p, len, ip, port);
|
return socksendto(s, p, len, ip, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int argsockfile(int n, struct socket **sp, struct file **fp);
|
||||||
|
|
||||||
SYSCALL_DEFINE(recvfrom)
|
SYSCALL_DEFINE(recvfrom)
|
||||||
{
|
{
|
||||||
struct socket *s;
|
struct socket *s;
|
||||||
|
struct file *file;
|
||||||
addr_t ubuf, uaddr, ulenp;
|
addr_t ubuf, uaddr, ulenp;
|
||||||
int len, flags, r;
|
int len, flags, r;
|
||||||
uint ip = 0;
|
uint ip = 0;
|
||||||
ushort port = 0;
|
ushort port = 0;
|
||||||
|
|
||||||
if ((r = argsock(0, &s)) < 0)
|
if ((r = argsockfile(0, &s, &file)) < 0)
|
||||||
return r;
|
return r;
|
||||||
argaddr(1, &ubuf);
|
argaddr(1, &ubuf);
|
||||||
argint(2, &len);
|
argint(2, &len);
|
||||||
|
|
@ -554,6 +562,13 @@ SYSCALL_DEFINE(recvfrom)
|
||||||
if (len && !p)
|
if (len && !p)
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
|
// As in recvmsg. This one blocked regardless, and musl's resolver -- which
|
||||||
|
// sends the A and AAAA queries together and reads its non-blocking socket
|
||||||
|
// until EAGAIN -- waited for ever after the first answer: NetSurf sat on
|
||||||
|
// "Loading" with the reply in hand.
|
||||||
|
if ((file->nonblock || (flags & MSG_DONTWAIT)) && !sockreadable(s))
|
||||||
|
return -EAGAIN;
|
||||||
|
|
||||||
r = sockrecvfrom(s, p, len, &ip, &port);
|
r = sockrecvfrom(s, p, len, &ip, &port);
|
||||||
if (r < 0 || uaddr == 0)
|
if (r < 0 || uaddr == 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,39 @@ sys_wait4(void)
|
||||||
return wpid;
|
return wpid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capabilities: all of them for root, none for anyone else -- which is what
|
||||||
|
// the permission checks here amount to. glycin asks before it sandboxes an
|
||||||
|
// image loader, and stopped at ENOSYS.
|
||||||
|
addr_t
|
||||||
|
sys_capget(void)
|
||||||
|
{
|
||||||
|
addr_t hdrp, datap;
|
||||||
|
uint32 hdr[2];
|
||||||
|
argaddr(0, &hdrp);
|
||||||
|
argaddr(1, &datap);
|
||||||
|
if (copy_from_user(hdr, hdrp, sizeof(hdr)) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
if (hdr[0] != 0x19980330 && hdr[0] != 0x20071026 && hdr[0] != 0x20080522) {
|
||||||
|
hdr[0] = 0x20080522; // the version this kernel speaks
|
||||||
|
put_user(hdr[0], hdrp);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (datap == 0)
|
||||||
|
return 0;
|
||||||
|
int words = hdr[0] == 0x19980330 ? 1 : 2;
|
||||||
|
uint32 all = myproc()->euid == 0 ? 0xffffffffu : 0;
|
||||||
|
for (int i = 0; i < words; i++) {
|
||||||
|
uint32 d[3] = { all, all, 0 }; // effective, permitted, inheritable
|
||||||
|
if (copy_to_user(datap + i * sizeof(d), d, sizeof(d)) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropping capabilities is accepted and changes nothing: there are none to
|
||||||
|
// drop that the uid does not already decide.
|
||||||
|
addr_t sys_capset(void) { return 0; }
|
||||||
|
|
||||||
addr_t
|
addr_t
|
||||||
sys_clone(void)
|
sys_clone(void)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,8 @@ SYSCALL(205, 284, eventfd)
|
||||||
SYSCALL(206, 73, flock)
|
SYSCALL(206, 73, flock)
|
||||||
SYSCALL(207, 51, getsockname)
|
SYSCALL(207, 51, getsockname)
|
||||||
SYSCALL(208, 52, getpeername)
|
SYSCALL(208, 52, getpeername)
|
||||||
|
SYSCALL(209, 125, capget)
|
||||||
|
SYSCALL(210, 126, capset)
|
||||||
|
|
||||||
// --- mounts ---
|
// --- mounts ---
|
||||||
SYSCALL(185, 165, mount)
|
SYSCALL(185, 165, mount)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ pkgs.mkShell {
|
||||||
qemu # make qemu, and every test harness
|
qemu # make qemu, and every test harness
|
||||||
OVMF # firmware for the UEFI path; the Makefile searches for it
|
OVMF # firmware for the UEFI path; the Makefile searches for it
|
||||||
|
|
||||||
|
bubblewrap # mkaroot runs Alpine's triggers in the root as /
|
||||||
xorriso # make iso
|
xorriso # make iso
|
||||||
mtools # ESP staging
|
mtools # ESP staging
|
||||||
curl gnutar # fetching ports
|
curl gnutar # fetching ports
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,49 @@ Section "ServerLayout"
|
||||||
EndSection
|
EndSection
|
||||||
XEOF
|
XEOF
|
||||||
|
|
||||||
|
# bwrap, without the sandbox. glycin -- which loads every image gdk-pixbuf is
|
||||||
|
# asked for, so every GTK icon -- runs its loaders under bubblewrap and has no
|
||||||
|
# other way to start them. Real bwrap needs user and mount namespaces, which
|
||||||
|
# SchOS does not have. This takes the same arguments, keeps what they say
|
||||||
|
# about the environment and the directory, and runs the program as it is:
|
||||||
|
# no isolation at all, and it says so in its name. It replaces bubblewrap's own
|
||||||
|
# /usr/bin/bwrap, which glycin depends on and which cannot run here.
|
||||||
|
mkdir -p "$P/usr/bin"
|
||||||
|
cat > "$P/usr/bin/bwrap" <<'BEOF'
|
||||||
|
#!/bin/sh
|
||||||
|
# SchOS: bubblewrap's command line, with no sandbox behind it. SchOS has no
|
||||||
|
# namespaces; what the options say about the environment and the working
|
||||||
|
# directory is honoured, and everything about isolation is ignored.
|
||||||
|
clear= envs=
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--clearenv) clear=1; shift ;;
|
||||||
|
--setenv) envs="$envs $2=$3"; shift 3 ;;
|
||||||
|
--unsetenv) unset "$2"; shift 2 ;;
|
||||||
|
--chdir) cd "$2" || exit 1; shift 2 ;;
|
||||||
|
--bind|--bind-try|--ro-bind|--ro-bind-try|--dev-bind|--dev-bind-try|\
|
||||||
|
--symlink|--bind-data|--ro-bind-data|--file|--chmod) shift 3 ;;
|
||||||
|
--dev|--proc|--tmpfs|--dir|--mqueue|--remount-ro|--seccomp|--add-seccomp-fd|\
|
||||||
|
--uid|--gid|--hostname|--cap-add|--cap-drop|--lock-file|--sync-fd|\
|
||||||
|
--info-fd|--json-status-fd|--block-fd|--userns-block-fd|--perms|--size|\
|
||||||
|
--argv0|--exec-label|--file-label|--userns|--userns2|--pidns) shift 2 ;;
|
||||||
|
--unshare-*|--share-net|--die-with-parent|--new-session|--as-pid-1|\
|
||||||
|
--disable-userns|--assert-userns-disabled) shift ;;
|
||||||
|
--) shift; break ;;
|
||||||
|
-*) echo "bwrap: $1: not understood (SchOS runs bwrap without a sandbox)" >&2
|
||||||
|
exit 1 ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ $# -gt 0 ] || { echo "bwrap: no command" >&2; exit 1; }
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if [ -n "$clear" ]; then exec env -i PATH="$PATH" $envs "$@"; fi
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
[ -n "$envs" ] && exec env $envs "$@"
|
||||||
|
exec "$@"
|
||||||
|
BEOF
|
||||||
|
chmod 755 "$P/usr/bin/bwrap"
|
||||||
|
|
||||||
# Mount points and runtime directories are alpine-baselayout's; an empty
|
# Mount points and runtime directories are alpine-baselayout's; an empty
|
||||||
# directory here only duplicates it.
|
# directory here only duplicates it.
|
||||||
for d in dev proc sys run tmp srv; do
|
for d in dev proc sys run tmp srv; do
|
||||||
|
|
@ -197,7 +240,7 @@ cp "$PKG/post-install" "$PKG/post-upgrade"
|
||||||
--info url:https://github.com/Nexoniarz/SchOS \
|
--info url:https://github.com/Nexoniarz/SchOS \
|
||||||
--info origin:schos-base \
|
--info origin:schos-base \
|
||||||
--info "depends:alpine-baselayout busybox musl apk-tools zsh" \
|
--info "depends:alpine-baselayout busybox musl apk-tools zsh" \
|
||||||
--info "replaces:alpine-release alpine-baselayout zsh" \
|
--info "replaces:alpine-release alpine-baselayout zsh bubblewrap" \
|
||||||
--files "$P" \
|
--files "$P" \
|
||||||
--script "post-install:$PKG/post-install" \
|
--script "post-install:$PKG/post-install" \
|
||||||
--script "post-upgrade:$PKG/post-upgrade" \
|
--script "post-upgrade:$PKG/post-upgrade" \
|
||||||
|
|
@ -234,6 +277,41 @@ ROOT_PI=$ROOT sh -c '
|
||||||
done
|
done
|
||||||
grep -qw schos "$ROOT_PI/etc/hosts" || printf "127.0.1.1\tschos\n" >> "$ROOT_PI/etc/hosts"'
|
grep -qw schos "$ROOT_PI/etc/hosts" || printf "127.0.1.1\tschos\n" >> "$ROOT_PI/etc/hosts"'
|
||||||
|
|
||||||
|
# The triggers --no-scripts skipped, and they are not optional: they build the
|
||||||
|
# caches half the desktop reads. Without shared-mime-info's, GLib cannot tell a
|
||||||
|
# PNG from anything else, so gdk-pixbuf -- which hands images to glycin by MIME
|
||||||
|
# type -- refused every icon and NetSurf died on its first toolbar button.
|
||||||
|
# Likewise gdk-pixbuf's loader list, GLib's compiled schemas and GIO modules,
|
||||||
|
# GTK's input methods, the font caches and busybox's applet links.
|
||||||
|
#
|
||||||
|
# They run in the root as /, which is what bubblewrap is for: no chroot, so no
|
||||||
|
# root needed. apk names each script by the hex form of the checksum the
|
||||||
|
# triggers database keys in base64, and passes the paths it watches as
|
||||||
|
# arguments, globs expanded.
|
||||||
|
if command -v bwrap >/dev/null 2>&1; then
|
||||||
|
TRIG=$(mktemp -d)
|
||||||
|
tar -xzf "$ROOT/lib/apk/db/scripts.tar.gz" -C "$TRIG" --wildcards '*.trigger' 2>/dev/null || true
|
||||||
|
mkdir -p "$ROOT/tmp"
|
||||||
|
for t in "$TRIG"/*.trigger; do
|
||||||
|
[ -e "$t" ] || continue
|
||||||
|
hex=${t##*.X1}; hex=${hex%.trigger}
|
||||||
|
q=Q1$(python3 -c 'import base64,sys; print(base64.b64encode(bytes.fromhex(sys.argv[1])).decode())' "$hex")
|
||||||
|
paths=$(grep "^$q " "$ROOT/lib/apk/db/triggers" | cut -d' ' -f2-)
|
||||||
|
cp "$t" "$ROOT/tmp/apk-trigger"
|
||||||
|
# --clearenv: the host's environment is not the root's. A Nix shell
|
||||||
|
# exports GDK_PIXBUF_MODULE_FILE, and the loader cache went there.
|
||||||
|
bwrap --bind "$ROOT" / --dev /dev --proc /proc --clearenv \
|
||||||
|
--setenv PATH /usr/sbin:/usr/bin:/sbin:/bin --setenv HOME /root \
|
||||||
|
/bin/sh -c 'a=; for g in '"$paths"'; do for p in $g; do [ -e "$p" ] && a="$a $p"; done; done
|
||||||
|
exec /bin/sh /tmp/apk-trigger $a' \
|
||||||
|
|| echo "mkaroot: trigger $(basename "$t") failed" >&2
|
||||||
|
rm -f "$ROOT/tmp/apk-trigger"
|
||||||
|
done
|
||||||
|
rm -rf "$TRIG"
|
||||||
|
else
|
||||||
|
echo "mkaroot: no bwrap -- triggers not run; image and MIME caches are missing" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
# The shared scratch directories, world-writable and sticky as baselayout
|
# The shared scratch directories, world-writable and sticky as baselayout
|
||||||
# means them. apk running as an ordinary user on the build host lands them as
|
# means them. apk running as an ordinary user on the build host lands them as
|
||||||
# 0755, and with the kernel reporting real modes that is what tmux and every
|
# 0755, and with the kernel reporting real modes that is what tmux and every
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue