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
|
|
@ -3,6 +3,7 @@
|
|||
#include "gfx/gfx.h"
|
||||
#include "core/param.h"
|
||||
#include "uapi/stat.h"
|
||||
#include "uapi/fcntl.h"
|
||||
#include "arch/memlayout.h"
|
||||
#include "arch/mmu.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
|
||||
// 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
|
||||
// sharing on purpose, and a private mapping of an anonymous object is an
|
||||
// expensive way to ask for MAP_ANONYMOUS.
|
||||
// A private mapping is allowed read-only, and is then the same pages
|
||||
// mapped without write access: until somebody writes, a private mapping
|
||||
// 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) {
|
||||
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;
|
||||
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)
|
||||
return -EINVAL;
|
||||
// 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) {
|
||||
char *pg = shm_page(id, (int)((offset + 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);
|
||||
shm_detach(id);
|
||||
return -ENOMEM;
|
||||
|
|
@ -517,6 +524,8 @@ struct shmseg {
|
|||
int used;
|
||||
int key;
|
||||
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 nattach; // live attachments across all processes
|
||||
char *pages[SHM_MAXPAGES]; // kernel virtual addresses
|
||||
|
|
@ -605,6 +614,7 @@ shm_resize(int id, uint64 newsize)
|
|||
{
|
||||
struct shmseg *sg;
|
||||
int want, i;
|
||||
uint64 len = newsize;
|
||||
|
||||
if (id < 0 || id >= NSHM)
|
||||
return -EINVAL;
|
||||
|
|
@ -644,6 +654,7 @@ shm_resize(int id, uint64 newsize)
|
|||
acquire(&shmlock);
|
||||
sg->npages = want;
|
||||
sg->size = newsize;
|
||||
sg->bytes = len;
|
||||
release(&shmlock);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -671,7 +682,11 @@ memfd_size(int id)
|
|||
return 0;
|
||||
shm_init_once();
|
||||
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);
|
||||
return n;
|
||||
}
|
||||
|
|
@ -679,9 +694,47 @@ memfd_size(int id)
|
|||
int
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// mmap understands. Wayland clients use ftruncate and mmap, but a descriptor
|
||||
// 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];
|
||||
if (!sg->used)
|
||||
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
|
||||
if (off + (uint64)n > sg->size)
|
||||
n = (int)(sg->size - off);
|
||||
if (off + (uint64)n > sg->bytes)
|
||||
n = (int)(sg->bytes - off);
|
||||
|
||||
while (done < n) {
|
||||
uint64 pos = off + (uint64)done;
|
||||
|
|
@ -751,6 +806,8 @@ sys_memfd_create(void)
|
|||
shm[i].size = 0;
|
||||
shm[i].npages = 0;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
|
@ -761,6 +818,8 @@ sys_memfd_create(void)
|
|||
if ((f = filealloc()) == 0) { shm_detach(id); return -ENFILE; }
|
||||
if ((fd = fdalloc(f)) < 0) { fileclose(f); return -EMFILE; }
|
||||
|
||||
if (flags & MFD_CLOEXEC)
|
||||
myproc()->files->flags[fd] = FD_CLOEXEC;
|
||||
f->type = FD_MEMFD;
|
||||
f->memfd_seg = id;
|
||||
f->readable = 1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue