summaryrefslogtreecommitdiffstats
path: root/arch/um/os-Linux
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /arch/um/os-Linux
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'arch/um/os-Linux')
-rw-r--r--arch/um/os-Linux/Makefile23
-rw-r--r--arch/um/os-Linux/elf_aux.c50
-rw-r--r--arch/um/os-Linux/execvp.c150
-rw-r--r--arch/um/os-Linux/file.c726
-rw-r--r--arch/um/os-Linux/helper.c236
-rw-r--r--arch/um/os-Linux/internal.h36
-rw-r--r--arch/um/os-Linux/irq.c145
-rw-r--r--arch/um/os-Linux/main.c265
-rw-r--r--arch/um/os-Linux/mem.c246
-rw-r--r--arch/um/os-Linux/process.c211
-rw-r--r--arch/um/os-Linux/registers.c43
-rw-r--r--arch/um/os-Linux/sigio.c304
-rw-r--r--arch/um/os-Linux/signal.c506
-rw-r--r--arch/um/os-Linux/skas/Makefile10
-rw-r--r--arch/um/os-Linux/skas/mem.c284
-rw-r--r--arch/um/os-Linux/skas/process.c908
-rw-r--r--arch/um/os-Linux/smp.c148
-rw-r--r--arch/um/os-Linux/start_up.c495
-rw-r--r--arch/um/os-Linux/time.c157
-rw-r--r--arch/um/os-Linux/tty.c60
-rw-r--r--arch/um/os-Linux/umid.c409
-rw-r--r--arch/um/os-Linux/user_syms.c37
-rw-r--r--arch/um/os-Linux/util.c209
23 files changed, 5658 insertions, 0 deletions
diff --git a/arch/um/os-Linux/Makefile b/arch/um/os-Linux/Makefile
new file mode 100644
index 000000000..f8d672d57
--- /dev/null
+++ b/arch/um/os-Linux/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+#
+
+# Don't instrument UML-specific code
+KCOV_INSTRUMENT := n
+
+obj-y = elf_aux.o execvp.o file.o helper.o irq.o main.o mem.o process.o \
+ registers.o sigio.o signal.o start_up.o time.o tty.o \
+ umid.o user_syms.o util.o skas/
+
+CFLAGS_signal.o += -Wframe-larger-than=4096
+
+CFLAGS_main.o += -Wno-frame-larger-than
+
+obj-$(CONFIG_SMP) += smp.o
+
+USER_OBJS := $(user-objs-y) elf_aux.o execvp.o file.o helper.o irq.o \
+ main.o mem.o process.o registers.o sigio.o signal.o start_up.o time.o \
+ tty.o umid.o util.o smp.o
+
+include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/um/os-Linux/elf_aux.c b/arch/um/os-Linux/elf_aux.c
new file mode 100644
index 000000000..72f416edf
--- /dev/null
+++ b/arch/um/os-Linux/elf_aux.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * arch/um/kernel/elf_aux.c
+ *
+ * Scan the ELF auxiliary vector provided by the host to extract
+ * information about vsyscall-page, etc.
+ *
+ * Copyright (C) 2004 Fujitsu Siemens Computers GmbH
+ * Author: Bodo Stroesser (bodo.stroesser@fujitsu-siemens.com)
+ */
+#include <elf.h>
+#include <stddef.h>
+#include <init.h>
+#include <elf_user.h>
+#include <mem_user.h>
+#include "internal.h"
+#include <linux/swab.h>
+
+#if __BITS_PER_LONG == 64
+typedef Elf64_auxv_t elf_auxv_t;
+#else
+typedef Elf32_auxv_t elf_auxv_t;
+#endif
+
+/* These are initialized very early in boot and never changed */
+char * elf_aux_platform;
+long elf_aux_hwcap;
+
+__init void scan_elf_aux( char **envp)
+{
+ elf_auxv_t * auxv;
+
+ while ( *envp++ != NULL) ;
+
+ for ( auxv = (elf_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) {
+ switch ( auxv->a_type ) {
+ case AT_HWCAP:
+ elf_aux_hwcap = auxv->a_un.a_val;
+ break;
+ case AT_PLATFORM:
+ /* elf.h removed the pointer elements from
+ * a_un, so we have to use a_val, which is
+ * all that's left.
+ */
+ elf_aux_platform =
+ (char *) (long) auxv->a_un.a_val;
+ break;
+ }
+ }
+}
diff --git a/arch/um/os-Linux/execvp.c b/arch/um/os-Linux/execvp.c
new file mode 100644
index 000000000..c09a5fd5e
--- /dev/null
+++ b/arch/um/os-Linux/execvp.c
@@ -0,0 +1,150 @@
+/* Copyright (C) 2006 by Paolo Giarrusso - modified from glibc' execvp.c.
+ Original copyright notice follows:
+
+ Copyright (C) 1991,92,1995-99,2002,2004 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+#include <unistd.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#ifndef TEST
+#include <um_malloc.h>
+#else
+#include <stdio.h>
+#define um_kmalloc malloc
+#endif
+#include <os.h>
+
+/* Execute FILE, searching in the `PATH' environment variable if it contains
+ no slashes, with arguments ARGV and environment from `environ'. */
+int execvp_noalloc(char *buf, const char *file, char *const argv[])
+{
+ if (*file == '\0') {
+ return -ENOENT;
+ }
+
+ if (strchr (file, '/') != NULL) {
+ /* Don't search when it contains a slash. */
+ execv(file, argv);
+ } else {
+ int got_eacces;
+ size_t len, pathlen;
+ char *name, *p;
+ char *path = getenv("PATH");
+ if (path == NULL)
+ path = ":/bin:/usr/bin";
+
+ len = strlen(file) + 1;
+ pathlen = strlen(path);
+ /* Copy the file name at the top. */
+ name = memcpy(buf + pathlen + 1, file, len);
+ /* And add the slash. */
+ *--name = '/';
+
+ got_eacces = 0;
+ p = path;
+ do {
+ char *startp;
+
+ path = p;
+ //Let's avoid this GNU extension.
+ //p = strchrnul (path, ':');
+ p = strchr(path, ':');
+ if (!p)
+ p = strchr(path, '\0');
+
+ if (p == path)
+ /* Two adjacent colons, or a colon at the beginning or the end
+ of `PATH' means to search the current directory. */
+ startp = name + 1;
+ else
+ startp = memcpy(name - (p - path), path, p - path);
+
+ /* Try to execute this name. If it works, execv will not return. */
+ execv(startp, argv);
+
+ /*
+ if (errno == ENOEXEC) {
+ }
+ */
+
+ switch (errno) {
+ case EACCES:
+ /* Record the we got a `Permission denied' error. If we end
+ up finding no executable we can use, we want to diagnose
+ that we did find one but were denied access. */
+ got_eacces = 1;
+ break;
+ case ENOENT:
+ case ESTALE:
+ case ENOTDIR:
+ /* Those errors indicate the file is missing or not executable
+ by us, in which case we want to just try the next path
+ directory. */
+ case ENODEV:
+ case ETIMEDOUT:
+ /* Some strange filesystems like AFS return even
+ stranger error numbers. They cannot reasonably mean
+ anything else so ignore those, too. */
+ case ENOEXEC:
+ /* We won't go searching for the shell
+ * if it is not executable - the Linux
+ * kernel already handles this enough,
+ * for us. */
+ break;
+
+ default:
+ /* Some other error means we found an executable file, but
+ something went wrong executing it; return the error to our
+ caller. */
+ return -errno;
+ }
+ } while (*p++ != '\0');
+
+ /* We tried every element and none of them worked. */
+ if (got_eacces)
+ /* At least one failure was due to permissions, so report that
+ error. */
+ return -EACCES;
+ }
+
+ /* Return the error from the last attempt (probably ENOENT). */
+ return -errno;
+}
+#ifdef TEST
+int main(int argc, char**argv)
+{
+ char buf[PATH_MAX];
+ int ret;
+ argc--;
+ if (!argc) {
+ os_warn("Not enough arguments\n");
+ return 1;
+ }
+ argv++;
+ if (ret = execvp_noalloc(buf, argv[0], argv)) {
+ errno = -ret;
+ perror("execvp_noalloc");
+ }
+ return 0;
+}
+#endif
diff --git a/arch/um/os-Linux/file.c b/arch/um/os-Linux/file.c
new file mode 100644
index 000000000..21f0e50fb
--- /dev/null
+++ b/arch/um/os-Linux/file.c
@@ -0,0 +1,726 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <linux/falloc.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/eventfd.h>
+#include <poll.h>
+#include <os.h>
+
+static void copy_stat(struct uml_stat *dst, const struct stat64 *src)
+{
+ *dst = ((struct uml_stat) {
+ .ust_dev = src->st_dev, /* device */
+ .ust_ino = src->st_ino, /* inode */
+ .ust_mode = src->st_mode, /* protection */
+ .ust_nlink = src->st_nlink, /* number of hard links */
+ .ust_uid = src->st_uid, /* user ID of owner */
+ .ust_gid = src->st_gid, /* group ID of owner */
+ .ust_size = src->st_size, /* total size, in bytes */
+ .ust_blksize = src->st_blksize, /* blocksize for filesys I/O */
+ .ust_blocks = src->st_blocks, /* number of blocks allocated */
+ .ust_atime = src->st_atime, /* time of last access */
+ .ust_mtime = src->st_mtime, /* time of last modification */
+ .ust_ctime = src->st_ctime, /* time of last change */
+ });
+}
+
+int os_stat_fd(const int fd, struct uml_stat *ubuf)
+{
+ struct stat64 sbuf;
+ int err;
+
+ CATCH_EINTR(err = fstat64(fd, &sbuf));
+ if (err < 0)
+ return -errno;
+
+ if (ubuf != NULL)
+ copy_stat(ubuf, &sbuf);
+ return err;
+}
+
+int os_stat_file(const char *file_name, struct uml_stat *ubuf)
+{
+ struct stat64 sbuf;
+ int err;
+
+ CATCH_EINTR(err = stat64(file_name, &sbuf));
+ if (err < 0)
+ return -errno;
+
+ if (ubuf != NULL)
+ copy_stat(ubuf, &sbuf);
+ return err;
+}
+
+int os_access(const char *file, int mode)
+{
+ int amode, err;
+
+ amode = (mode & OS_ACC_R_OK ? R_OK : 0) |
+ (mode & OS_ACC_W_OK ? W_OK : 0) |
+ (mode & OS_ACC_X_OK ? X_OK : 0) |
+ (mode & OS_ACC_F_OK ? F_OK : 0);
+
+ err = access(file, amode);
+ if (err < 0)
+ return -errno;
+
+ return 0;
+}
+
+/* FIXME? required only by hostaudio (because it passes ioctls verbatim) */
+int os_ioctl_generic(int fd, unsigned int cmd, unsigned long arg)
+{
+ int err;
+
+ err = ioctl(fd, cmd, arg);
+ if (err < 0)
+ return -errno;
+
+ return err;
+}
+
+/* FIXME: ensure namebuf in os_get_if_name is big enough */
+int os_get_ifname(int fd, char* namebuf)
+{
+ if (ioctl(fd, SIOCGIFNAME, namebuf) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int os_mode_fd(int fd, int mode)
+{
+ int err;
+
+ CATCH_EINTR(err = fchmod(fd, mode));
+ if (err < 0)
+ return -errno;
+
+ return 0;
+}
+
+int os_file_type(char *file)
+{
+ struct uml_stat buf;
+ int err;
+
+ err = os_stat_file(file, &buf);
+ if (err < 0)
+ return err;
+
+ if (S_ISDIR(buf.ust_mode))
+ return OS_TYPE_DIR;
+ else if (S_ISLNK(buf.ust_mode))
+ return OS_TYPE_SYMLINK;
+ else if (S_ISCHR(buf.ust_mode))
+ return OS_TYPE_CHARDEV;
+ else if (S_ISBLK(buf.ust_mode))
+ return OS_TYPE_BLOCKDEV;
+ else if (S_ISFIFO(buf.ust_mode))
+ return OS_TYPE_FIFO;
+ else if (S_ISSOCK(buf.ust_mode))
+ return OS_TYPE_SOCK;
+ else return OS_TYPE_FILE;
+}
+
+int os_file_mode(const char *file, struct openflags *mode_out)
+{
+ int err;
+
+ *mode_out = OPENFLAGS();
+
+ err = access(file, W_OK);
+ if (err && (errno != EACCES))
+ return -errno;
+ else if (!err)
+ *mode_out = of_write(*mode_out);
+
+ err = access(file, R_OK);
+ if (err && (errno != EACCES))
+ return -errno;
+ else if (!err)
+ *mode_out = of_read(*mode_out);
+
+ return err;
+}
+
+int os_open_file(const char *file, struct openflags flags, int mode)
+{
+ int fd, err, f = 0;
+
+ if (flags.r && flags.w)
+ f = O_RDWR;
+ else if (flags.r)
+ f = O_RDONLY;
+ else if (flags.w)
+ f = O_WRONLY;
+ else f = 0;
+
+ if (flags.s)
+ f |= O_SYNC;
+ if (flags.c)
+ f |= O_CREAT;
+ if (flags.t)
+ f |= O_TRUNC;
+ if (flags.e)
+ f |= O_EXCL;
+ if (flags.a)
+ f |= O_APPEND;
+
+ fd = open64(file, f, mode);
+ if (fd < 0)
+ return -errno;
+
+ if (flags.cl && fcntl(fd, F_SETFD, 1)) {
+ err = -errno;
+ close(fd);
+ return err;
+ }
+
+ return fd;
+}
+
+int os_connect_socket(const char *name)
+{
+ struct sockaddr_un sock;
+ int fd, err;
+
+ sock.sun_family = AF_UNIX;
+ snprintf(sock.sun_path, sizeof(sock.sun_path), "%s", name);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ err = -errno;
+ goto out;
+ }
+
+ err = connect(fd, (struct sockaddr *) &sock, sizeof(sock));
+ if (err) {
+ err = -errno;
+ goto out_close;
+ }
+
+ return fd;
+
+out_close:
+ close(fd);
+out:
+ return err;
+}
+
+int os_dup_file(int fd)
+{
+ int new_fd = dup(fd);
+
+ if (new_fd < 0)
+ return -errno;
+
+ return new_fd;
+}
+
+void os_close_file(int fd)
+{
+ close(fd);
+}
+
+int os_seek_file(int fd, unsigned long long offset)
+{
+ unsigned long long actual;
+
+ actual = lseek64(fd, offset, SEEK_SET);
+ if (actual != offset)
+ return -errno;
+ return 0;
+}
+
+int os_read_file(int fd, void *buf, int len)
+{
+ int n = read(fd, buf, len);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_pread_file(int fd, void *buf, int len, unsigned long long offset)
+{
+ int n = pread(fd, buf, len, offset);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_write_file(int fd, const void *buf, int len)
+{
+ int n = write(fd, (void *) buf, len);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_sync_file(int fd)
+{
+ int n = fdatasync(fd);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_pwrite_file(int fd, const void *buf, int len, unsigned long long offset)
+{
+ int n = pwrite(fd, (void *) buf, len, offset);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+
+int os_file_size(const char *file, unsigned long long *size_out)
+{
+ struct uml_stat buf;
+ int err;
+
+ err = os_stat_file(file, &buf);
+ if (err < 0) {
+ printk(UM_KERN_ERR "Couldn't stat \"%s\" : err = %d\n", file,
+ -err);
+ return err;
+ }
+
+ if (S_ISBLK(buf.ust_mode)) {
+ int fd;
+ long blocks;
+
+ fd = open(file, O_RDONLY, 0);
+ if (fd < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "Couldn't open \"%s\", "
+ "errno = %d\n", file, errno);
+ return err;
+ }
+ if (ioctl(fd, BLKGETSIZE, &blocks) < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "Couldn't get the block size of "
+ "\"%s\", errno = %d\n", file, errno);
+ close(fd);
+ return err;
+ }
+ *size_out = ((long long) blocks) * 512;
+ close(fd);
+ }
+ else *size_out = buf.ust_size;
+
+ return 0;
+}
+
+int os_file_modtime(const char *file, long long *modtime)
+{
+ struct uml_stat buf;
+ int err;
+
+ err = os_stat_file(file, &buf);
+ if (err < 0) {
+ printk(UM_KERN_ERR "Couldn't stat \"%s\" : err = %d\n", file,
+ -err);
+ return err;
+ }
+
+ *modtime = buf.ust_mtime;
+ return 0;
+}
+
+int os_set_exec_close(int fd)
+{
+ int err;
+
+ CATCH_EINTR(err = fcntl(fd, F_SETFD, FD_CLOEXEC));
+
+ if (err < 0)
+ return -errno;
+ return err;
+}
+
+int os_pipe(int *fds, int stream, int close_on_exec)
+{
+ int err, type = stream ? SOCK_STREAM : SOCK_DGRAM;
+
+ err = socketpair(AF_UNIX, type, 0, fds);
+ if (err < 0)
+ return -errno;
+
+ if (!close_on_exec)
+ return 0;
+
+ err = os_set_exec_close(fds[0]);
+ if (err < 0)
+ goto error;
+
+ err = os_set_exec_close(fds[1]);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+ error:
+ printk(UM_KERN_ERR "os_pipe : Setting FD_CLOEXEC failed, err = %d\n",
+ -err);
+ close(fds[1]);
+ close(fds[0]);
+ return err;
+}
+
+int os_set_fd_async(int fd)
+{
+ int err, flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ flags |= O_ASYNC | O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "os_set_fd_async : failed to set O_ASYNC "
+ "and O_NONBLOCK on fd # %d, errno = %d\n", fd, errno);
+ return err;
+ }
+
+ if ((fcntl(fd, F_SETSIG, SIGIO) < 0) ||
+ (fcntl(fd, F_SETOWN, os_getpid()) < 0)) {
+ err = -errno;
+ printk(UM_KERN_ERR "os_set_fd_async : Failed to fcntl F_SETOWN "
+ "(or F_SETSIG) fd %d, errno = %d\n", fd, errno);
+ return err;
+ }
+
+ return 0;
+}
+
+int os_clear_fd_async(int fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ flags &= ~(O_ASYNC | O_NONBLOCK);
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return -errno;
+ return 0;
+}
+
+int os_set_fd_block(int fd, int blocking)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int os_accept_connection(int fd)
+{
+ int new;
+
+ new = accept(fd, NULL, 0);
+ if (new < 0)
+ return -errno;
+ return new;
+}
+
+#ifndef SHUT_RD
+#define SHUT_RD 0
+#endif
+
+#ifndef SHUT_WR
+#define SHUT_WR 1
+#endif
+
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+int os_shutdown_socket(int fd, int r, int w)
+{
+ int what, err;
+
+ if (r && w)
+ what = SHUT_RDWR;
+ else if (r)
+ what = SHUT_RD;
+ else if (w)
+ what = SHUT_WR;
+ else
+ return -EINVAL;
+
+ err = shutdown(fd, what);
+ if (err < 0)
+ return -errno;
+ return 0;
+}
+
+/**
+ * os_rcv_fd_msg - receive message with (optional) FDs
+ * @fd: the FD to receive from
+ * @fds: the array for FDs to write to
+ * @n_fds: number of FDs to receive (@fds array size)
+ * @data: the message buffer
+ * @data_len: the size of the message to receive
+ *
+ * Receive a message with FDs.
+ *
+ * Returns: the size of the received message, or an error code
+ */
+ssize_t os_rcv_fd_msg(int fd, int *fds, unsigned int n_fds,
+ void *data, size_t data_len)
+{
+#define MAX_RCV_FDS 2
+ char buf[CMSG_SPACE(sizeof(*fds) * MAX_RCV_FDS)];
+ struct cmsghdr *cmsg;
+ struct iovec iov = {
+ .iov_base = data,
+ .iov_len = data_len,
+ };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = buf,
+ .msg_controllen = CMSG_SPACE(sizeof(*fds) * n_fds),
+ };
+ int n;
+
+ if (n_fds > MAX_RCV_FDS)
+ return -EINVAL;
+
+ n = recvmsg(fd, &msg, 0);
+ if (n < 0)
+ return -errno;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS)
+ return n;
+
+ memcpy(fds, CMSG_DATA(cmsg), cmsg->cmsg_len - CMSG_LEN(0));
+ return n;
+}
+
+int os_create_unix_socket(const char *file, int len, int close_on_exec)
+{
+ struct sockaddr_un addr;
+ int sock, err;
+
+ sock = socket(PF_UNIX, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return -errno;
+
+ if (close_on_exec) {
+ err = os_set_exec_close(sock);
+ if (err < 0)
+ printk(UM_KERN_ERR "create_unix_socket : "
+ "close_on_exec failed, err = %d", -err);
+ }
+
+ addr.sun_family = AF_UNIX;
+
+ snprintf(addr.sun_path, len, "%s", file);
+
+ err = bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+ if (err < 0)
+ return -errno;
+
+ return sock;
+}
+
+void os_flush_stdout(void)
+{
+ fflush(stdout);
+}
+
+int os_lock_file(int fd, int excl)
+{
+ int type = excl ? F_WRLCK : F_RDLCK;
+ struct flock lock = ((struct flock) { .l_type = type,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0 } );
+ int err, save;
+
+ err = fcntl(fd, F_SETLK, &lock);
+ if (!err)
+ goto out;
+
+ save = -errno;
+ err = fcntl(fd, F_GETLK, &lock);
+ if (err) {
+ err = -errno;
+ goto out;
+ }
+
+ printk(UM_KERN_ERR "F_SETLK failed, file already locked by pid %d\n",
+ lock.l_pid);
+ err = save;
+ out:
+ return err;
+}
+
+unsigned os_major(unsigned long long dev)
+{
+ return major(dev);
+}
+
+unsigned os_minor(unsigned long long dev)
+{
+ return minor(dev);
+}
+
+unsigned long long os_makedev(unsigned major, unsigned minor)
+{
+ return makedev(major, minor);
+}
+
+int os_falloc_punch(int fd, unsigned long long offset, int len)
+{
+ int n = fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, len);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_falloc_zeroes(int fd, unsigned long long offset, int len)
+{
+ int n = fallocate(fd, FALLOC_FL_ZERO_RANGE|FALLOC_FL_KEEP_SIZE, offset, len);
+
+ if (n < 0)
+ return -errno;
+ return n;
+}
+
+int os_eventfd(unsigned int initval, int flags)
+{
+ int fd = eventfd(initval, flags);
+
+ if (fd < 0)
+ return -errno;
+ return fd;
+}
+
+int os_sendmsg_fds(int fd, const void *buf, unsigned int len, const int *fds,
+ unsigned int fds_num)
+{
+ struct iovec iov = {
+ .iov_base = (void *) buf,
+ .iov_len = len,
+ };
+ union {
+ char control[CMSG_SPACE(sizeof(*fds) * OS_SENDMSG_MAX_FDS)];
+ struct cmsghdr align;
+ } u;
+ unsigned int fds_size = sizeof(*fds) * fds_num;
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = u.control,
+ .msg_controllen = CMSG_SPACE(fds_size),
+ };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ int err;
+
+ if (fds_num > OS_SENDMSG_MAX_FDS)
+ return -EINVAL;
+ memset(u.control, 0, sizeof(u.control));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(fds_size);
+ memcpy(CMSG_DATA(cmsg), fds, fds_size);
+ err = sendmsg(fd, &msg, 0);
+
+ if (err < 0)
+ return -errno;
+ return err;
+}
+
+int os_poll(unsigned int n, const int *fds)
+{
+ /* currently need 2 FDs at most so avoid dynamic allocation */
+ struct pollfd pollfds[2] = {};
+ unsigned int i;
+ int ret;
+
+ if (n > ARRAY_SIZE(pollfds))
+ return -EINVAL;
+
+ for (i = 0; i < n; i++) {
+ pollfds[i].fd = fds[i];
+ pollfds[i].events = POLLIN;
+ }
+
+ ret = poll(pollfds, n, -1);
+ if (ret < 0)
+ return -errno;
+
+ /* Return the index of the available FD */
+ for (i = 0; i < n; i++) {
+ if (pollfds[i].revents)
+ return i;
+ }
+
+ return -EIO;
+}
+
+void *os_mmap_rw_shared(int fd, size_t size)
+{
+ void *res = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (res == MAP_FAILED)
+ return NULL;
+
+ return res;
+}
+
+void *os_mremap_rw_shared(void *old_addr, size_t old_size, size_t new_size)
+{
+ void *res;
+
+ res = mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE, NULL);
+
+ if (res == MAP_FAILED)
+ return NULL;
+
+ return res;
+}
diff --git a/arch/um/os-Linux/helper.c b/arch/um/os-Linux/helper.c
new file mode 100644
index 000000000..89c2ad2a4
--- /dev/null
+++ b/arch/um/os-Linux/helper.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sched.h>
+#include <pthread.h>
+#include <linux/limits.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <kern_util.h>
+#include <os.h>
+#include <um_malloc.h>
+
+struct helper_data {
+ void (*pre_exec)(void*);
+ void *pre_data;
+ char **argv;
+ int fd;
+ char *buf;
+};
+
+static int helper_child(void *arg)
+{
+ struct helper_data *data = arg;
+ char **argv = data->argv;
+ int err, ret;
+
+ if (data->pre_exec != NULL)
+ (*data->pre_exec)(data->pre_data);
+ err = execvp_noalloc(data->buf, argv[0], argv);
+
+ /* If the exec succeeds, we don't get here */
+ CATCH_EINTR(ret = write(data->fd, &err, sizeof(err)));
+
+ return 0;
+}
+
+/* Returns either the pid of the child process we run or -E* on failure. */
+int run_helper(void (*pre_exec)(void *), void *pre_data, char **argv)
+{
+ struct helper_data data;
+ unsigned long stack, sp;
+ int pid, fds[2], ret, n;
+
+ stack = alloc_stack(0, __uml_cant_sleep());
+ if (stack == 0)
+ return -ENOMEM;
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ if (ret < 0) {
+ ret = -errno;
+ printk(UM_KERN_ERR "run_helper : pipe failed, errno = %d\n",
+ errno);
+ goto out_free;
+ }
+
+ ret = os_set_exec_close(fds[1]);
+ if (ret < 0) {
+ printk(UM_KERN_ERR "run_helper : setting FD_CLOEXEC failed, "
+ "ret = %d\n", -ret);
+ goto out_close;
+ }
+
+ sp = stack + UM_KERN_PAGE_SIZE;
+ data.pre_exec = pre_exec;
+ data.pre_data = pre_data;
+ data.argv = argv;
+ data.fd = fds[1];
+ data.buf = __uml_cant_sleep() ? uml_kmalloc(PATH_MAX, UM_GFP_ATOMIC) :
+ uml_kmalloc(PATH_MAX, UM_GFP_KERNEL);
+ pid = clone(helper_child, (void *) sp, CLONE_VM, &data);
+ if (pid < 0) {
+ ret = -errno;
+ printk(UM_KERN_ERR "run_helper : clone failed, errno = %d\n",
+ errno);
+ goto out_free2;
+ }
+
+ close(fds[1]);
+ fds[1] = -1;
+
+ /*
+ * Read the errno value from the child, if the exec failed, or get 0 if
+ * the exec succeeded because the pipe fd was set as close-on-exec.
+ */
+ n = read(fds[0], &ret, sizeof(ret));
+ if (n == 0) {
+ ret = pid;
+ } else {
+ if (n < 0) {
+ n = -errno;
+ printk(UM_KERN_ERR "run_helper : read on pipe failed, "
+ "ret = %d\n", -n);
+ ret = n;
+ }
+ CATCH_EINTR(waitpid(pid, NULL, __WALL));
+ }
+
+ if (ret < 0)
+ printk(UM_KERN_ERR "run_helper : failed to exec %s on host: %s\n",
+ argv[0], strerror(-ret));
+
+out_free2:
+ kfree(data.buf);
+out_close:
+ if (fds[1] != -1)
+ close(fds[1]);
+ close(fds[0]);
+out_free:
+ free_stack(stack, 0);
+ return ret;
+}
+
+int run_helper_thread(int (*proc)(void *), void *arg, unsigned int flags,
+ unsigned long *stack_out)
+{
+ unsigned long stack, sp;
+ int pid, status, err;
+
+ /* To share memory space, use os_run_helper_thread() instead. */
+ if (flags & CLONE_VM)
+ return -EINVAL;
+
+ stack = alloc_stack(0, __uml_cant_sleep());
+ if (stack == 0)
+ return -ENOMEM;
+
+ sp = stack + UM_KERN_PAGE_SIZE;
+ pid = clone(proc, (void *) sp, flags, arg);
+ if (pid < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "run_helper_thread : clone failed, "
+ "errno = %d\n", errno);
+ return err;
+ }
+ if (stack_out == NULL) {
+ CATCH_EINTR(pid = waitpid(pid, &status, __WALL));
+ if (pid < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "run_helper_thread - wait failed, "
+ "errno = %d\n", errno);
+ pid = err;
+ }
+ if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0))
+ printk(UM_KERN_ERR "run_helper_thread - thread "
+ "returned status 0x%x\n", status);
+ free_stack(stack, 0);
+ } else
+ *stack_out = stack;
+ return pid;
+}
+
+int helper_wait(int pid)
+{
+ int ret, status;
+ int wflags = __WALL;
+
+ CATCH_EINTR(ret = waitpid(pid, &status, wflags));
+ if (ret < 0) {
+ printk(UM_KERN_ERR "helper_wait : waitpid process %d failed, "
+ "errno = %d\n", pid, errno);
+ return -errno;
+ } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ printk(UM_KERN_ERR "helper_wait : process %d exited with "
+ "status 0x%x\n", pid, status);
+ return -ECHILD;
+ } else
+ return 0;
+}
+
+struct os_helper_thread {
+ pthread_t handle;
+};
+
+int os_run_helper_thread(struct os_helper_thread **td_out,
+ void *(*routine)(void *), void *arg)
+{
+ struct os_helper_thread *td;
+ sigset_t sigset, oset;
+ int err, flags;
+
+ flags = __uml_cant_sleep() ? UM_GFP_ATOMIC : UM_GFP_KERNEL;
+ td = uml_kmalloc(sizeof(*td), flags);
+ if (!td)
+ return -ENOMEM;
+
+ sigfillset(&sigset);
+ if (sigprocmask(SIG_SETMASK, &sigset, &oset) < 0) {
+ err = -errno;
+ kfree(td);
+ return err;
+ }
+
+ err = pthread_create(&td->handle, NULL, routine, arg);
+
+ if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0)
+ panic("Failed to restore the signal mask: %d", errno);
+
+ if (err != 0)
+ kfree(td);
+ else
+ *td_out = td;
+
+ return -err;
+}
+
+void os_kill_helper_thread(struct os_helper_thread *td)
+{
+ pthread_cancel(td->handle);
+ pthread_join(td->handle, NULL);
+ kfree(td);
+}
+
+void os_fix_helper_thread_signals(void)
+{
+ sigset_t sigset;
+
+ sigemptyset(&sigset);
+
+ sigaddset(&sigset, SIGWINCH);
+ sigaddset(&sigset, SIGPIPE);
+ sigaddset(&sigset, SIGPROF);
+ sigaddset(&sigset, SIGINT);
+ sigaddset(&sigset, SIGTERM);
+ sigaddset(&sigset, SIGCHLD);
+ sigaddset(&sigset, SIGALRM);
+ sigaddset(&sigset, SIGIO);
+ sigaddset(&sigset, SIGUSR1);
+
+ pthread_sigmask(SIG_SETMASK, &sigset, NULL);
+}
diff --git a/arch/um/os-Linux/internal.h b/arch/um/os-Linux/internal.h
new file mode 100644
index 000000000..bac9fcc8c
--- /dev/null
+++ b/arch/um/os-Linux/internal.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UM_OS_LINUX_INTERNAL_H
+#define __UM_OS_LINUX_INTERNAL_H
+
+#include <mm_id.h>
+#include <stub-data.h>
+#include <signal.h>
+
+/*
+ * elf_aux.c
+ */
+void scan_elf_aux(char **envp);
+
+/*
+ * mem.c
+ */
+void check_tmpexec(void);
+
+/*
+ * signal.c
+ */
+extern __thread int signals_enabled;
+int timer_alarm_pending(void);
+
+/*
+ * skas/process.c
+ */
+void wait_stub_done(int pid);
+void wait_stub_done_seccomp(struct mm_id *mm_idp, int running, int wait_sigsys);
+
+/*
+ * smp.c
+ */
+#define IPI_SIGNAL SIGRTMIN
+
+#endif /* __UM_OS_LINUX_INTERNAL_H */
diff --git a/arch/um/os-Linux/irq.c b/arch/um/os-Linux/irq.c
new file mode 100644
index 000000000..cf7e49c08
--- /dev/null
+++ b/arch/um/os-Linux/irq.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017 - Cambridge Greys Ltd
+ * Copyright (C) 2011 - 2014 Cisco Systems Inc
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <signal.h>
+#include <string.h>
+#include <irq_user.h>
+#include <os.h>
+#include <um_malloc.h>
+
+/* Epoll support */
+
+static int epollfd = -1;
+
+#define MAX_EPOLL_EVENTS 64
+
+static struct epoll_event epoll_events[MAX_EPOLL_EVENTS];
+
+/* Helper to return an Epoll data pointer from an epoll event structure.
+ * We need to keep this one on the userspace side to keep includes separate
+ */
+
+void *os_epoll_get_data_pointer(int index)
+{
+ return epoll_events[index].data.ptr;
+}
+
+/* Helper to compare events versus the events in the epoll structure.
+ * Same as above - needs to be on the userspace side
+ */
+
+
+int os_epoll_triggered(int index, int events)
+{
+ return epoll_events[index].events & events;
+}
+/* Helper to set the event mask.
+ * The event mask is opaque to the kernel side, because it does not have
+ * access to the right includes/defines for EPOLL constants.
+ */
+
+int os_event_mask(enum um_irq_type irq_type)
+{
+ if (irq_type == IRQ_READ)
+ return EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP | EPOLLRDHUP;
+ if (irq_type == IRQ_WRITE)
+ return EPOLLOUT;
+ return 0;
+}
+
+/*
+ * Initial Epoll Setup
+ */
+int os_setup_epoll(void)
+{
+ epollfd = epoll_create(MAX_EPOLL_EVENTS);
+ return epollfd;
+}
+
+/*
+ * Helper to run the actual epoll_wait
+ */
+int os_waiting_for_events_epoll(void)
+{
+ int n, err;
+
+ n = epoll_wait(epollfd,
+ (struct epoll_event *) &epoll_events, MAX_EPOLL_EVENTS, 0);
+ if (n < 0) {
+ err = -errno;
+ if (errno != EINTR)
+ printk(
+ UM_KERN_ERR "os_waiting_for_events:"
+ " epoll returned %d, error = %s\n", n,
+ strerror(errno)
+ );
+ return err;
+ }
+ return n;
+}
+
+
+/*
+ * Helper to add a fd to epoll
+ */
+int os_add_epoll_fd(int events, int fd, void *data)
+{
+ struct epoll_event event;
+ int result;
+
+ event.data.ptr = data;
+ event.events = events | EPOLLET;
+ result = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
+ if ((result) && (errno == EEXIST))
+ result = os_mod_epoll_fd(events, fd, data);
+ if (result)
+ printk("epollctl add err fd %d, %s\n", fd, strerror(errno));
+ return result;
+}
+
+/*
+ * Helper to mod the fd event mask and/or data backreference
+ */
+int os_mod_epoll_fd(int events, int fd, void *data)
+{
+ struct epoll_event event;
+ int result;
+
+ event.data.ptr = data;
+ event.events = events;
+ result = epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
+ if (result)
+ printk(UM_KERN_ERR
+ "epollctl mod err fd %d, %s\n", fd, strerror(errno));
+ return result;
+}
+
+/*
+ * Helper to delete the epoll fd
+ */
+int os_del_epoll_fd(int fd)
+{
+ struct epoll_event event;
+ /* This is quiet as we use this as IO ON/OFF - so it is often
+ * invoked on a non-existent fd
+ */
+ return epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event);
+}
+
+void os_set_ioignore(void)
+{
+ signal(SIGIO, SIG_IGN);
+}
+
+void os_close_epoll_fd(void)
+{
+ /* Needed so we do not leak an fd when rebooting */
+ os_close_file(epollfd);
+}
diff --git a/arch/um/os-Linux/main.c b/arch/um/os-Linux/main.c
new file mode 100644
index 000000000..7e114862a
--- /dev/null
+++ b/arch/um/os-Linux/main.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de)
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/personality.h>
+#include <as-layout.h>
+#include <init.h>
+#include <kern_util.h>
+#include <os.h>
+#include <um_malloc.h>
+#include "internal.h"
+
+#define STACKSIZE (8 * 1024 * 1024)
+
+static void __init set_stklim(void)
+{
+ struct rlimit lim;
+
+ if (getrlimit(RLIMIT_STACK, &lim) < 0) {
+ perror("getrlimit");
+ exit(1);
+ }
+ if ((lim.rlim_cur == RLIM_INFINITY) || (lim.rlim_cur > STACKSIZE)) {
+ lim.rlim_cur = STACKSIZE;
+ if (setrlimit(RLIMIT_STACK, &lim) < 0) {
+ perror("setrlimit");
+ exit(1);
+ }
+ }
+}
+
+static void last_ditch_exit(int sig)
+{
+ uml_cleanup();
+ exit(1);
+}
+
+static void __init install_fatal_handler(int sig)
+{
+ struct sigaction action;
+
+ /* All signals are enabled in this handler ... */
+ sigemptyset(&action.sa_mask);
+
+ /*
+ * ... including the signal being handled, plus we want the
+ * handler reset to the default behavior, so that if an exit
+ * handler is hanging for some reason, the UML will just die
+ * after this signal is sent a second time.
+ */
+ action.sa_flags = SA_RESETHAND | SA_NODEFER;
+ action.sa_restorer = NULL;
+ action.sa_handler = last_ditch_exit;
+ if (sigaction(sig, &action, NULL) < 0) {
+ os_warn("failed to install handler for signal %d "
+ "- errno = %d\n", sig, errno);
+ exit(1);
+ }
+}
+
+#define UML_LIB_PATH ":" OS_LIB_PATH "/uml"
+
+static void __init setup_env_path(void)
+{
+ char *new_path = NULL;
+ char *old_path = NULL;
+ int path_len = 0;
+
+ old_path = getenv("PATH");
+ /*
+ * if no PATH variable is set or it has an empty value
+ * just use the default + /usr/lib/uml
+ */
+ if (!old_path || (path_len = strlen(old_path)) == 0) {
+ if (putenv("PATH=:/bin:/usr/bin/" UML_LIB_PATH))
+ perror("couldn't putenv");
+ return;
+ }
+
+ /* append /usr/lib/uml to the existing path */
+ path_len += strlen("PATH=" UML_LIB_PATH) + 1;
+ new_path = malloc(path_len);
+ if (!new_path) {
+ perror("couldn't malloc to set a new PATH");
+ return;
+ }
+ snprintf(new_path, path_len, "PATH=%s" UML_LIB_PATH, old_path);
+ if (putenv(new_path)) {
+ perror("couldn't putenv to set a new PATH");
+ free(new_path);
+ }
+}
+
+int __init main(int argc, char **argv, char **envp)
+{
+ char **new_argv;
+ int ret, i, err;
+
+ /* Disable randomization and re-exec if it was changed successfully */
+ ret = personality(PER_LINUX | ADDR_NO_RANDOMIZE);
+ if (ret >= 0 && (ret & (PER_LINUX | ADDR_NO_RANDOMIZE)) !=
+ (PER_LINUX | ADDR_NO_RANDOMIZE)) {
+ char buf[4096] = {};
+ ssize_t ret;
+
+ ret = readlink("/proc/self/exe", buf, sizeof(buf));
+ if (ret < 0 || ret >= sizeof(buf)) {
+ perror("readlink failure");
+ exit(1);
+ }
+ execve(buf, argv, envp);
+ }
+
+ set_stklim();
+
+ setup_env_path();
+
+ setsid();
+
+ new_argv = malloc((argc + 1) * sizeof(char *));
+ if (new_argv == NULL) {
+ perror("Mallocing argv");
+ exit(1);
+ }
+ for (i = 0; i < argc; i++) {
+ new_argv[i] = strdup(argv[i]);
+ if (new_argv[i] == NULL) {
+ perror("Mallocing an arg");
+ exit(1);
+ }
+ }
+ new_argv[argc] = NULL;
+
+ /*
+ * Allow these signals to bring down a UML if all other
+ * methods of control fail.
+ */
+ install_fatal_handler(SIGINT);
+ install_fatal_handler(SIGTERM);
+
+ scan_elf_aux(envp);
+
+ change_sig(SIGPIPE, 0);
+ ret = linux_main(argc, argv, envp);
+
+ /*
+ * Disable SIGPROF - I have no idea why libc doesn't do this or turn
+ * off the profiling time, but UML dies with a SIGPROF just before
+ * exiting when profiling is active.
+ */
+ change_sig(SIGPROF, 0);
+
+ /*
+ * This signal stuff used to be in the reboot case. However,
+ * sometimes a timer signal can come in when we're halting (reproducably
+ * when writing out gcov information, presumably because that takes
+ * some time) and cause a segfault.
+ */
+
+ /* stop timers and set timer signal to be ignored */
+ os_timer_disable(0);
+
+ /* disable SIGIO for the fds and set SIGIO to be ignored */
+ err = deactivate_all_fds();
+ if (err)
+ os_warn("deactivate_all_fds failed, errno = %d\n", -err);
+
+ /*
+ * Let any pending signals fire now. This ensures
+ * that they won't be delivered after the exec, when
+ * they are definitely not expected.
+ */
+ unblock_signals();
+
+ os_info("\n");
+ /* Reboot */
+ if (ret) {
+ execvp(new_argv[0], new_argv);
+ perror("Failed to exec kernel");
+ ret = 1;
+ }
+ return uml_exitcode;
+}
+
+extern void *__real_malloc(int);
+extern void __real_free(void *);
+
+/* workaround for -Wmissing-prototypes warnings */
+void *__wrap_malloc(int size);
+void *__wrap_calloc(int n, int size);
+void __wrap_free(void *ptr);
+
+void *__wrap_malloc(int size)
+{
+ void *ret;
+
+ if (!kmalloc_ok)
+ return __real_malloc(size);
+ else if (size <= UM_KERN_PAGE_SIZE)
+ /* finding contiguous pages can be hard*/
+ ret = uml_kmalloc(size, UM_GFP_KERNEL);
+ else ret = vmalloc(size);
+
+ /*
+ * glibc people insist that if malloc fails, errno should be
+ * set by malloc as well. So we do.
+ */
+ if (ret == NULL)
+ errno = ENOMEM;
+
+ return ret;
+}
+
+void *__wrap_calloc(int n, int size)
+{
+ void *ptr = __wrap_malloc(n * size);
+
+ if (ptr == NULL)
+ return NULL;
+ memset(ptr, 0, n * size);
+ return ptr;
+}
+
+void __wrap_free(void *ptr)
+{
+ unsigned long addr = (unsigned long) ptr;
+
+ /*
+ * We need to know how the allocation happened, so it can be correctly
+ * freed. This is done by seeing what region of memory the pointer is
+ * in -
+ * physical memory - kmalloc/kfree
+ * kernel virtual memory - vmalloc/vfree
+ * anywhere else - malloc/free
+ * If kmalloc is not yet possible, then either high_physmem and/or
+ * end_vm are still 0 (as at startup), in which case we call free, or
+ * we have set them, but anyway addr has not been allocated from those
+ * areas. So, in both cases __real_free is called.
+ *
+ * CAN_KMALLOC is checked because it would be bad to free a buffer
+ * with kmalloc/vmalloc after they have been turned off during
+ * shutdown.
+ * XXX: However, we sometimes shutdown CAN_KMALLOC temporarily, so
+ * there is a possibility for memory leaks.
+ */
+
+ if ((addr >= uml_physmem) && (addr < high_physmem)) {
+ if (kmalloc_ok)
+ kfree(ptr);
+ }
+ else if ((addr >= start_vm) && (addr < end_vm)) {
+ if (kmalloc_ok)
+ vfree(ptr);
+ }
+ else __real_free(ptr);
+}
diff --git a/arch/um/os-Linux/mem.c b/arch/um/os-Linux/mem.c
new file mode 100644
index 000000000..72f302f4d
--- /dev/null
+++ b/arch/um/os-Linux/mem.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+#include <init.h>
+#include <kern_util.h>
+#include <os.h>
+#include "internal.h"
+
+/*
+ * kasan_map_memory - maps memory from @start with a size of @len.
+ * The allocated memory is filled with zeroes upon success.
+ * @start: the start address of the memory to be mapped
+ * @len: the length of the memory to be mapped
+ *
+ * This function is used to map shadow memory for KASAN in uml
+ */
+void kasan_map_memory(void *start, size_t len)
+{
+ if (mmap(start,
+ len,
+ PROT_READ|PROT_WRITE,
+ MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE,
+ -1,
+ 0) == MAP_FAILED) {
+ os_info("Couldn't allocate shadow memory: %s\n.",
+ strerror(errno));
+ exit(1);
+ }
+
+ if (madvise(start, len, MADV_DONTDUMP)) {
+ os_info("Couldn't set MAD_DONTDUMP on shadow memory: %s\n.",
+ strerror(errno));
+ exit(1);
+ }
+
+ if (madvise(start, len, MADV_DONTFORK)) {
+ os_info("Couldn't set MADV_DONTFORK on shadow memory: %s\n.",
+ strerror(errno));
+ exit(1);
+ }
+}
+
+/* Set by make_tempfile() during early boot. */
+char *tempdir = NULL;
+
+/* Check if dir is on tmpfs. Return 0 if yes, -1 if no or error. */
+static int __init check_tmpfs(const char *dir)
+{
+ struct statfs st;
+
+ os_info("Checking if %s is on tmpfs...", dir);
+ if (statfs(dir, &st) < 0) {
+ os_info("%s\n", strerror(errno));
+ } else if (st.f_type != TMPFS_MAGIC) {
+ os_info("no\n");
+ } else {
+ os_info("OK\n");
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Choose the tempdir to use. We want something on tmpfs so that our memory is
+ * not subject to the host's vm.dirty_ratio. If a tempdir is specified in the
+ * environment, we use that even if it's not on tmpfs, but we warn the user.
+ * Otherwise, we try common tmpfs locations, and if no tmpfs directory is found
+ * then we fall back to /tmp.
+ */
+static char * __init choose_tempdir(void)
+{
+ static const char * const vars[] = {
+ "TMPDIR",
+ "TMP",
+ "TEMP",
+ NULL
+ };
+ static const char fallback_dir[] = "/tmp";
+ static const char * const tmpfs_dirs[] = {
+ "/dev/shm",
+ fallback_dir,
+ NULL
+ };
+ int i;
+ const char *dir;
+
+ os_info("Checking environment variables for a tempdir...");
+ for (i = 0; vars[i]; i++) {
+ dir = getenv(vars[i]);
+ if ((dir != NULL) && (*dir != '\0')) {
+ os_info("%s\n", dir);
+ if (check_tmpfs(dir) >= 0)
+ goto done;
+ else
+ goto warn;
+ }
+ }
+ os_info("none found\n");
+
+ for (i = 0; tmpfs_dirs[i]; i++) {
+ dir = tmpfs_dirs[i];
+ if (check_tmpfs(dir) >= 0)
+ goto done;
+ }
+
+ dir = fallback_dir;
+warn:
+ os_warn("Warning: tempdir %s is not on tmpfs\n", dir);
+done:
+ /* Make a copy since getenv results may not remain valid forever. */
+ return strdup(dir);
+}
+
+/*
+ * Create an unlinked tempfile in a suitable tempdir. template must be the
+ * basename part of the template with a leading '/'.
+ */
+static int __init make_tempfile(const char *template)
+{
+ char *tempname;
+ int fd;
+
+ if (tempdir == NULL) {
+ tempdir = choose_tempdir();
+ if (tempdir == NULL) {
+ os_warn("Failed to choose tempdir: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ }
+
+#ifdef O_TMPFILE
+ fd = open(tempdir, O_CLOEXEC | O_RDWR | O_EXCL | O_TMPFILE, 0700);
+ /*
+ * If the running system does not support O_TMPFILE flag then retry
+ * without it.
+ */
+ if (fd != -1 || (errno != EINVAL && errno != EISDIR &&
+ errno != EOPNOTSUPP))
+ return fd;
+#endif
+
+ tempname = malloc(strlen(tempdir) + strlen(template) + 1);
+ if (tempname == NULL)
+ return -1;
+
+ strcpy(tempname, tempdir);
+ strcat(tempname, template);
+ fd = mkstemp(tempname);
+ if (fd < 0) {
+ os_warn("open - cannot create %s: %s\n", tempname,
+ strerror(errno));
+ goto out;
+ }
+ if (unlink(tempname) < 0) {
+ perror("unlink");
+ goto close;
+ }
+ free(tempname);
+ return fd;
+close:
+ close(fd);
+out:
+ free(tempname);
+ return -1;
+}
+
+#define TEMPNAME_TEMPLATE "/vm_file-XXXXXX"
+
+static int __init create_tmp_file(unsigned long long len)
+{
+ int fd, err;
+ char zero;
+
+ fd = make_tempfile(TEMPNAME_TEMPLATE);
+ if (fd < 0)
+ exit(1);
+
+ /*
+ * Seek to len - 1 because writing a character there will
+ * increase the file size by one byte, to the desired length.
+ */
+ if (lseek64(fd, len - 1, SEEK_SET) < 0) {
+ perror("lseek64");
+ exit(1);
+ }
+
+ zero = 0;
+
+ err = write(fd, &zero, 1);
+ if (err != 1) {
+ perror("write");
+ exit(1);
+ }
+
+ return fd;
+}
+
+int __init create_mem_file(unsigned long long len)
+{
+ int err, fd;
+
+ fd = create_tmp_file(len);
+
+ err = os_set_exec_close(fd);
+ if (err < 0) {
+ errno = -err;
+ perror("exec_close");
+ }
+ return fd;
+}
+
+void __init check_tmpexec(void)
+{
+ void *addr;
+ int err, fd = create_tmp_file(UM_KERN_PAGE_SIZE);
+
+ addr = mmap(NULL, UM_KERN_PAGE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, fd, 0);
+ os_info("Checking PROT_EXEC mmap in %s...", tempdir);
+ if (addr == MAP_FAILED) {
+ err = errno;
+ os_warn("%s\n", strerror(err));
+ close(fd);
+ if (err == EPERM)
+ os_warn("%s must be not mounted noexec\n", tempdir);
+ exit(1);
+ }
+ os_info("OK\n");
+ munmap(addr, UM_KERN_PAGE_SIZE);
+
+ close(fd);
+}
diff --git a/arch/um/os-Linux/process.c b/arch/um/os-Linux/process.c
new file mode 100644
index 000000000..3a2a84ab9
--- /dev/null
+++ b/arch/um/os-Linux/process.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de)
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/futex.h>
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <asm/unistd.h>
+#include <init.h>
+#include <longjmp.h>
+#include <os.h>
+#include <skas/skas.h>
+
+void os_alarm_process(int pid)
+{
+ if (pid <= 0)
+ return;
+
+ kill(pid, SIGALRM);
+}
+
+void os_kill_process(int pid, int reap_child)
+{
+ if (pid <= 0)
+ return;
+
+ /* Block signals until child is reaped */
+ block_signals();
+
+ kill(pid, SIGKILL);
+ if (reap_child)
+ CATCH_EINTR(waitpid(pid, NULL, __WALL));
+
+ unblock_signals();
+}
+
+/* Kill off a ptraced child by all means available. kill it normally first,
+ * then PTRACE_KILL it, then PTRACE_CONT it in case it's in a run state from
+ * which it can't exit directly.
+ */
+
+void os_kill_ptraced_process(int pid, int reap_child)
+{
+ if (pid <= 0)
+ return;
+
+ /* Block signals until child is reaped */
+ block_signals();
+
+ kill(pid, SIGKILL);
+ ptrace(PTRACE_KILL, pid);
+ ptrace(PTRACE_CONT, pid);
+ if (reap_child)
+ CATCH_EINTR(waitpid(pid, NULL, __WALL));
+
+ unblock_signals();
+}
+
+pid_t os_reap_child(void)
+{
+ int status;
+
+ /* Try to reap a child */
+ return waitpid(-1, &status, WNOHANG);
+}
+
+/* Don't use the glibc version, which caches the result in TLS. It misses some
+ * syscalls, and also breaks with clone(), which does not unshare the TLS.
+ */
+
+int os_getpid(void)
+{
+ return syscall(__NR_getpid);
+}
+
+int os_map_memory(void *virt, int fd, unsigned long long off, unsigned long len,
+ int r, int w, int x)
+{
+ void *loc;
+ int prot;
+
+ prot = (r ? PROT_READ : 0) | (w ? PROT_WRITE : 0) |
+ (x ? PROT_EXEC : 0);
+
+ loc = mmap64((void *) virt, len, prot, MAP_SHARED | MAP_FIXED,
+ fd, off);
+ if (loc == MAP_FAILED)
+ return -errno;
+ return 0;
+}
+
+int os_protect_memory(void *addr, unsigned long len, int r, int w, int x)
+{
+ int prot = ((r ? PROT_READ : 0) | (w ? PROT_WRITE : 0) |
+ (x ? PROT_EXEC : 0));
+
+ if (mprotect(addr, len, prot) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int os_unmap_memory(void *addr, int len)
+{
+ int err;
+
+ err = munmap(addr, len);
+ if (err < 0)
+ return -errno;
+ return 0;
+}
+
+#ifndef MADV_REMOVE
+#define MADV_REMOVE KERNEL_MADV_REMOVE
+#endif
+
+int os_drop_memory(void *addr, int length)
+{
+ int err;
+
+ err = madvise(addr, length, MADV_REMOVE);
+ if (err < 0)
+ err = -errno;
+ return err;
+}
+
+int __init can_drop_memory(void)
+{
+ void *addr;
+ int fd, ok = 0;
+
+ printk(UM_KERN_INFO "Checking host MADV_REMOVE support...");
+ fd = create_mem_file(UM_KERN_PAGE_SIZE);
+ if (fd < 0) {
+ printk(UM_KERN_ERR "Creating test memory file failed, "
+ "err = %d\n", -fd);
+ goto out;
+ }
+
+ addr = mmap64(NULL, UM_KERN_PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) {
+ printk(UM_KERN_ERR "Mapping test memory file failed, "
+ "err = %d\n", -errno);
+ goto out_close;
+ }
+
+ if (madvise(addr, UM_KERN_PAGE_SIZE, MADV_REMOVE) != 0) {
+ printk(UM_KERN_ERR "MADV_REMOVE failed, err = %d\n", -errno);
+ goto out_unmap;
+ }
+
+ printk(UM_KERN_CONT "OK\n");
+ ok = 1;
+
+out_unmap:
+ munmap(addr, UM_KERN_PAGE_SIZE);
+out_close:
+ close(fd);
+out:
+ return ok;
+}
+
+void init_new_thread_signals(void)
+{
+ set_handler(SIGSEGV);
+ set_handler(SIGTRAP);
+ set_handler(SIGFPE);
+ set_handler(SIGILL);
+ set_handler(SIGBUS);
+ signal(SIGHUP, SIG_IGN);
+ set_handler(SIGIO);
+ /* We (currently) only use the child reaper IRQ in seccomp mode */
+ if (using_seccomp)
+ set_handler(SIGCHLD);
+ signal(SIGWINCH, SIG_IGN);
+}
+
+void os_set_pdeathsig(void)
+{
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+}
+
+int os_futex_wait(void *uaddr, unsigned int val)
+{
+ int r;
+
+ CATCH_EINTR(r = syscall(__NR_futex, uaddr, FUTEX_WAIT, val,
+ NULL, NULL, 0));
+ return r < 0 ? -errno : r;
+}
+
+int os_futex_wake(void *uaddr)
+{
+ int r;
+
+ CATCH_EINTR(r = syscall(__NR_futex, uaddr, FUTEX_WAKE, INT_MAX,
+ NULL, NULL, 0));
+ return r < 0 ? -errno : r;
+}
diff --git a/arch/um/os-Linux/registers.c b/arch/um/os-Linux/registers.c
new file mode 100644
index 000000000..bfba2cbc9
--- /dev/null
+++ b/arch/um/os-Linux/registers.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2004 PathScale, Inc
+ * Copyright (C) 2004 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sysdep/ptrace.h>
+#include <sysdep/ptrace_user.h>
+#include <registers.h>
+#include <stdlib.h>
+
+/* This is set once at boot time and not changed thereafter */
+
+unsigned long exec_regs[MAX_REG_NR];
+unsigned long *exec_fp_regs;
+
+int init_pid_registers(int pid)
+{
+ int err;
+
+ err = ptrace(PTRACE_GETREGS, pid, 0, exec_regs);
+ if (err < 0)
+ return -errno;
+
+ err = arch_init_registers(pid);
+ if (err < 0)
+ return err;
+
+ exec_fp_regs = malloc(host_fp_size);
+ get_fp_registers(pid, exec_fp_regs);
+ return 0;
+}
+
+void get_safe_registers(unsigned long *regs, unsigned long *fp_regs)
+{
+ memcpy(regs, exec_regs, sizeof(exec_regs));
+
+ if (fp_regs)
+ memcpy(fp_regs, exec_fp_regs, host_fp_size);
+}
diff --git a/arch/um/os-Linux/sigio.c b/arch/um/os-Linux/sigio.c
new file mode 100644
index 000000000..6de145f8f
--- /dev/null
+++ b/arch/um/os-Linux/sigio.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2002 - 2008 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pty.h>
+#include <sched.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <asm/unistd.h>
+#include <kern_util.h>
+#include <init.h>
+#include <os.h>
+#include <sigio.h>
+#include <um_malloc.h>
+
+/*
+ * Protected by sigio_lock(), also used by sigio_cleanup, which is an
+ * exitcall.
+ */
+static struct os_helper_thread *write_sigio_td;
+
+static int epollfd = -1;
+
+#define MAX_EPOLL_EVENTS 64
+
+static struct epoll_event epoll_events[MAX_EPOLL_EVENTS];
+
+static void *write_sigio_thread(void *unused)
+{
+ int pid = getpid();
+ int r;
+
+ os_fix_helper_thread_signals();
+
+ while (1) {
+ r = epoll_wait(epollfd, epoll_events, MAX_EPOLL_EVENTS, -1);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ printk(UM_KERN_ERR "%s: epoll_wait failed, errno = %d\n",
+ __func__, errno);
+ }
+
+ CATCH_EINTR(r = syscall(__NR_tgkill, pid, pid, SIGIO));
+ if (r < 0)
+ printk(UM_KERN_ERR "%s: tgkill failed, errno = %d\n",
+ __func__, errno);
+ }
+
+ return NULL;
+}
+
+int __add_sigio_fd(int fd)
+{
+ struct epoll_event event = {
+ .data.fd = fd,
+ .events = EPOLLIN | EPOLLET,
+ };
+ int r;
+
+ CATCH_EINTR(r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event));
+ return r < 0 ? -errno : 0;
+}
+
+int add_sigio_fd(int fd)
+{
+ int err;
+
+ sigio_lock();
+ err = __add_sigio_fd(fd);
+ sigio_unlock();
+
+ return err;
+}
+
+int __ignore_sigio_fd(int fd)
+{
+ struct epoll_event event;
+ int r;
+
+ CATCH_EINTR(r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event));
+ return r < 0 ? -errno : 0;
+}
+
+int ignore_sigio_fd(int fd)
+{
+ int err;
+
+ sigio_lock();
+ err = __ignore_sigio_fd(fd);
+ sigio_unlock();
+
+ return err;
+}
+
+static void write_sigio_workaround(void)
+{
+ int err;
+
+ sigio_lock();
+ if (write_sigio_td)
+ goto out;
+
+ epollfd = epoll_create(MAX_EPOLL_EVENTS);
+ if (epollfd < 0) {
+ printk(UM_KERN_ERR "%s: epoll_create failed, errno = %d\n",
+ __func__, errno);
+ goto out;
+ }
+
+ err = os_run_helper_thread(&write_sigio_td, write_sigio_thread, NULL);
+ if (err < 0) {
+ printk(UM_KERN_ERR "%s: os_run_helper_thread failed, errno = %d\n",
+ __func__, -err);
+ close(epollfd);
+ epollfd = -1;
+ goto out;
+ }
+
+out:
+ sigio_unlock();
+}
+
+void sigio_broken(void)
+{
+ write_sigio_workaround();
+}
+
+/* Changed during early boot */
+static int pty_output_sigio;
+
+void maybe_sigio_broken(int fd)
+{
+ if (!isatty(fd))
+ return;
+
+ if (pty_output_sigio)
+ return;
+
+ sigio_broken();
+}
+
+static void sigio_cleanup(void)
+{
+ if (!write_sigio_td)
+ return;
+
+ os_kill_helper_thread(write_sigio_td);
+ write_sigio_td = NULL;
+}
+
+__uml_exitcall(sigio_cleanup);
+
+/* Used as a flag during SIGIO testing early in boot */
+static int got_sigio;
+
+static void __init handler(int sig)
+{
+ got_sigio = 1;
+}
+
+struct openpty_arg {
+ int master;
+ int slave;
+ int err;
+};
+
+static void openpty_cb(void *arg)
+{
+ struct openpty_arg *info = arg;
+
+ info->err = 0;
+ if (openpty(&info->master, &info->slave, NULL, NULL, NULL))
+ info->err = -errno;
+}
+
+static int async_pty(int master, int slave)
+{
+ int flags;
+
+ flags = fcntl(master, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ if ((fcntl(master, F_SETFL, flags | O_NONBLOCK | O_ASYNC) < 0) ||
+ (fcntl(master, F_SETOWN, os_getpid()) < 0))
+ return -errno;
+
+ if ((fcntl(slave, F_SETFL, flags | O_NONBLOCK) < 0))
+ return -errno;
+
+ return 0;
+}
+
+static void __init check_one_sigio(void (*proc)(int, int))
+{
+ struct sigaction old, new;
+ struct openpty_arg pty = { .master = -1, .slave = -1 };
+ int master, slave, err;
+
+ initial_thread_cb(openpty_cb, &pty);
+ if (pty.err) {
+ printk(UM_KERN_ERR "check_one_sigio failed, errno = %d\n",
+ -pty.err);
+ return;
+ }
+
+ master = pty.master;
+ slave = pty.slave;
+
+ if ((master == -1) || (slave == -1)) {
+ printk(UM_KERN_ERR "check_one_sigio failed to allocate a "
+ "pty\n");
+ return;
+ }
+
+ /* Not now, but complain so we now where we failed. */
+ err = raw(master);
+ if (err < 0) {
+ printk(UM_KERN_ERR "check_one_sigio : raw failed, errno = %d\n",
+ -err);
+ return;
+ }
+
+ err = async_pty(master, slave);
+ if (err < 0) {
+ printk(UM_KERN_ERR "check_one_sigio : sigio_async failed, "
+ "err = %d\n", -err);
+ return;
+ }
+
+ if (sigaction(SIGIO, NULL, &old) < 0) {
+ printk(UM_KERN_ERR "check_one_sigio : sigaction 1 failed, "
+ "errno = %d\n", errno);
+ return;
+ }
+
+ new = old;
+ new.sa_handler = handler;
+ if (sigaction(SIGIO, &new, NULL) < 0) {
+ printk(UM_KERN_ERR "check_one_sigio : sigaction 2 failed, "
+ "errno = %d\n", errno);
+ return;
+ }
+
+ got_sigio = 0;
+ (*proc)(master, slave);
+
+ close(master);
+ close(slave);
+
+ if (sigaction(SIGIO, &old, NULL) < 0)
+ printk(UM_KERN_ERR "check_one_sigio : sigaction 3 failed, "
+ "errno = %d\n", errno);
+}
+
+static void tty_output(int master, int slave)
+{
+ int n;
+ char buf[512];
+
+ printk(UM_KERN_INFO "Checking that host ptys support output SIGIO...");
+
+ memset(buf, 0, sizeof(buf));
+
+ while (write(master, buf, sizeof(buf)) > 0) ;
+ if (errno != EAGAIN)
+ printk(UM_KERN_ERR "tty_output : write failed, errno = %d\n",
+ errno);
+ while (((n = read(slave, buf, sizeof(buf))) > 0) &&
+ !({ barrier(); got_sigio; }))
+ ;
+
+ if (got_sigio) {
+ printk(UM_KERN_CONT "Yes\n");
+ pty_output_sigio = 1;
+ } else if (n == -EAGAIN)
+ printk(UM_KERN_CONT "No, enabling workaround\n");
+ else
+ printk(UM_KERN_CONT "tty_output : read failed, err = %d\n", n);
+}
+
+static void __init check_sigio(void)
+{
+ if ((access("/dev/ptmx", R_OK) < 0) &&
+ (access("/dev/ptyp0", R_OK) < 0)) {
+ printk(UM_KERN_WARNING "No pseudo-terminals available - "
+ "skipping pty SIGIO check\n");
+ return;
+ }
+ check_one_sigio(tty_output);
+}
+
+/* Here because it only does the SIGIO testing for now */
+void __init os_check_bugs(void)
+{
+ check_sigio();
+}
diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
new file mode 100644
index 000000000..6c993bc8c
--- /dev/null
+++ b/arch/um/os-Linux/signal.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Anton Ivanov (aivanov@{brocade.com,kot-begemot.co.uk})
+ * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de)
+ * Copyright (C) 2004 PathScale, Inc
+ * Copyright (C) 2004 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <as-layout.h>
+#include <kern_util.h>
+#include <os.h>
+#include <skas.h>
+#include <sysdep/mcontext.h>
+#include <um_malloc.h>
+#include <sys/ucontext.h>
+#include <timetravel.h>
+#include "internal.h"
+
+void (*sig_info[NSIG])(int, struct siginfo *, struct uml_pt_regs *, void *mc) = {
+ [SIGTRAP] = relay_signal,
+ [SIGFPE] = relay_signal,
+ [SIGILL] = relay_signal,
+ [SIGWINCH] = winch,
+ [SIGBUS] = relay_signal,
+ [SIGSEGV] = segv_handler,
+ [SIGIO] = sigio_handler,
+ [SIGCHLD] = sigchld_handler,
+};
+
+static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
+{
+ struct uml_pt_regs r;
+
+ r.is_user = 0;
+ if (sig == SIGSEGV) {
+ /* For segfaults, we want the data from the sigcontext. */
+ get_regs_from_mc(&r, mc);
+ GET_FAULTINFO_FROM_MC(r.faultinfo, mc);
+ }
+
+ /* enable signals if sig isn't IRQ signal */
+ if ((sig != SIGIO) && (sig != SIGWINCH) && (sig != SIGCHLD))
+ unblock_signals_trace();
+
+ (*sig_info[sig])(sig, si, &r, mc);
+}
+
+/*
+ * These are the asynchronous signals. SIGPROF is excluded because we want to
+ * be able to profile all of UML, not just the non-critical sections. If
+ * profiling is not thread-safe, then that is not my problem. We can disable
+ * profiling when SMP is enabled in that case.
+ */
+#define SIGIO_BIT 0
+#define SIGIO_MASK (1 << SIGIO_BIT)
+
+#define SIGALRM_BIT 1
+#define SIGALRM_MASK (1 << SIGALRM_BIT)
+
+#define SIGCHLD_BIT 2
+#define SIGCHLD_MASK (1 << SIGCHLD_BIT)
+
+__thread int signals_enabled;
+#if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
+static int signals_blocked, signals_blocked_pending;
+#endif
+static __thread unsigned int signals_pending;
+static __thread unsigned int signals_active;
+
+static void sig_handler(int sig, struct siginfo *si, mcontext_t *mc)
+{
+ int enabled = signals_enabled;
+
+#if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
+ if ((signals_blocked ||
+ __atomic_load_n(&signals_blocked_pending, __ATOMIC_SEQ_CST)) &&
+ (sig == SIGIO)) {
+ /* increment so unblock will do another round */
+ __atomic_add_fetch(&signals_blocked_pending, 1,
+ __ATOMIC_SEQ_CST);
+ return;
+ }
+#endif
+
+ if (!enabled && (sig == SIGIO)) {
+ /*
+ * In TT_MODE_EXTERNAL, need to still call time-travel
+ * handlers. This will mark signals_pending by itself
+ * (only if necessary.)
+ * Note we won't get here if signals are hard-blocked
+ * (which is handled above), in that case the hard-
+ * unblock will handle things.
+ */
+ if (time_travel_mode == TT_MODE_EXTERNAL)
+ sigio_run_timetravel_handlers();
+ else
+ signals_pending |= SIGIO_MASK;
+ return;
+ }
+
+ if (!enabled && (sig == SIGCHLD)) {
+ signals_pending |= SIGCHLD_MASK;
+ return;
+ }
+
+ block_signals_trace();
+
+ sig_handler_common(sig, si, mc);
+
+ um_set_signals_trace(enabled);
+}
+
+static void timer_real_alarm_handler(mcontext_t *mc)
+{
+ struct uml_pt_regs regs;
+
+ if (mc != NULL)
+ get_regs_from_mc(&regs, mc);
+ else
+ memset(&regs, 0, sizeof(regs));
+ timer_handler(SIGALRM, NULL, &regs);
+}
+
+static void timer_alarm_handler(int sig, struct siginfo *unused_si, mcontext_t *mc)
+{
+ int enabled;
+
+ enabled = signals_enabled;
+ if (!signals_enabled) {
+ signals_pending |= SIGALRM_MASK;
+ return;
+ }
+
+ block_signals_trace();
+
+ signals_active |= SIGALRM_MASK;
+
+ timer_real_alarm_handler(mc);
+
+ signals_active &= ~SIGALRM_MASK;
+
+ um_set_signals_trace(enabled);
+}
+
+void deliver_alarm(void) {
+ timer_alarm_handler(SIGALRM, NULL, NULL);
+}
+
+void timer_set_signal_handler(void)
+{
+ set_handler(SIGALRM);
+}
+
+int timer_alarm_pending(void)
+{
+ return !!(signals_pending & SIGALRM_MASK);
+}
+
+void set_sigstack(void *sig_stack, int size)
+{
+ stack_t stack = {
+ .ss_flags = 0,
+ .ss_sp = sig_stack,
+ .ss_size = size
+ };
+
+ if (sigaltstack(&stack, NULL) != 0)
+ panic("enabling signal stack failed, errno = %d\n", errno);
+}
+
+static void sigusr1_handler(int sig, struct siginfo *unused_si, mcontext_t *mc)
+{
+ uml_pm_wake();
+}
+
+void register_pm_wake_signal(void)
+{
+ set_handler(SIGUSR1);
+}
+
+static void (*handlers[_NSIG])(int sig, struct siginfo *si, mcontext_t *mc) = {
+ [SIGSEGV] = sig_handler,
+ [SIGBUS] = sig_handler,
+ [SIGILL] = sig_handler,
+ [SIGFPE] = sig_handler,
+ [SIGTRAP] = sig_handler,
+
+ [SIGIO] = sig_handler,
+ [SIGWINCH] = sig_handler,
+ /* SIGCHLD is only actually registered in seccomp mode. */
+ [SIGCHLD] = sig_handler,
+ [SIGALRM] = timer_alarm_handler,
+
+ [SIGUSR1] = sigusr1_handler,
+};
+
+static void hard_handler(int sig, siginfo_t *si, void *p)
+{
+ ucontext_t *uc = p;
+ mcontext_t *mc = &uc->uc_mcontext;
+ int save_errno = errno;
+
+ (*handlers[sig])(sig, (struct siginfo *)si, mc);
+
+ errno = save_errno;
+}
+
+void set_handler(int sig)
+{
+ struct sigaction action;
+ int flags = SA_SIGINFO | SA_ONSTACK;
+ sigset_t sig_mask;
+
+ action.sa_sigaction = hard_handler;
+
+ /* block irq ones */
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGIO);
+ sigaddset(&action.sa_mask, SIGWINCH);
+ sigaddset(&action.sa_mask, SIGALRM);
+ if (using_seccomp)
+ sigaddset(&action.sa_mask, SIGCHLD);
+
+ if (sig == SIGSEGV)
+ flags |= SA_NODEFER;
+
+ if (sigismember(&action.sa_mask, sig))
+ flags |= SA_RESTART; /* if it's an irq signal */
+
+ action.sa_flags = flags;
+ action.sa_restorer = NULL;
+ if (sigaction(sig, &action, NULL) < 0)
+ panic("sigaction failed - errno = %d\n", errno);
+
+ sigemptyset(&sig_mask);
+ sigaddset(&sig_mask, sig);
+ if (sigprocmask(SIG_UNBLOCK, &sig_mask, NULL) < 0)
+ panic("sigprocmask failed - errno = %d\n", errno);
+}
+
+void send_sigio_to_self(void)
+{
+ kill(os_getpid(), SIGIO);
+}
+
+int change_sig(int signal, int on)
+{
+ sigset_t sigset;
+
+ sigemptyset(&sigset);
+ sigaddset(&sigset, signal);
+ if (sigprocmask(on ? SIG_UNBLOCK : SIG_BLOCK, &sigset, NULL) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static inline void __block_signals(void)
+{
+ if (!signals_enabled)
+ return;
+
+ os_local_ipi_disable();
+ barrier();
+ signals_enabled = 0;
+}
+
+static inline void __unblock_signals(void)
+{
+ if (signals_enabled)
+ return;
+
+ signals_enabled = 1;
+ barrier();
+ os_local_ipi_enable();
+}
+
+void block_signals(void)
+{
+ __block_signals();
+ /*
+ * This must return with signals disabled, so this barrier
+ * ensures that writes are flushed out before the return.
+ * This might matter if gcc figures out how to inline this and
+ * decides to shuffle this code into the caller.
+ */
+ barrier();
+}
+
+void unblock_signals(void)
+{
+ int save_pending;
+
+ if (signals_enabled == 1)
+ return;
+
+ __unblock_signals();
+
+#if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
+ deliver_time_travel_irqs();
+#endif
+
+ /*
+ * We loop because the IRQ handler returns with interrupts off. So,
+ * interrupts may have arrived and we need to re-enable them and
+ * recheck signals_pending.
+ */
+ while (1) {
+ /*
+ * Save and reset save_pending after enabling signals. This
+ * way, signals_pending won't be changed while we're reading it.
+ *
+ * Setting signals_enabled and reading signals_pending must
+ * happen in this order, so have the barrier here.
+ */
+ barrier();
+
+ save_pending = signals_pending;
+ if (save_pending == 0)
+ return;
+
+ signals_pending = 0;
+
+ /*
+ * We have pending interrupts, so disable signals, as the
+ * handlers expect them off when they are called. They will
+ * be enabled again above. We need to trace this, as we're
+ * expected to be enabling interrupts already, but any more
+ * tracing that happens inside the handlers we call for the
+ * pending signals will mess up the tracing state.
+ */
+ __block_signals();
+ um_trace_signals_off();
+
+ /*
+ * Deal with SIGIO first because the alarm handler might
+ * schedule, leaving the pending SIGIO stranded until we come
+ * back here.
+ *
+ * SIGIO's handler doesn't use siginfo or mcontext,
+ * so they can be NULL.
+ */
+ if (save_pending & SIGIO_MASK)
+ sig_handler_common(SIGIO, NULL, NULL);
+
+ if (save_pending & SIGCHLD_MASK) {
+ struct uml_pt_regs regs = {};
+
+ sigchld_handler(SIGCHLD, NULL, &regs, NULL);
+ }
+
+ /* Do not reenter the handler */
+
+ if ((save_pending & SIGALRM_MASK) && (!(signals_active & SIGALRM_MASK)))
+ timer_real_alarm_handler(NULL);
+
+ /* Rerun the loop only if there is still pending SIGIO and not in TIMER handler */
+
+ if (!(signals_pending & SIGIO_MASK) && (signals_active & SIGALRM_MASK))
+ return;
+
+ /* Re-enable signals and trace that we're doing so. */
+ um_trace_signals_on();
+ __unblock_signals();
+ }
+}
+
+int um_get_signals(void)
+{
+ return signals_enabled;
+}
+
+int um_set_signals(int enable)
+{
+ int ret;
+ if (signals_enabled == enable)
+ return enable;
+
+ ret = signals_enabled;
+ if (enable)
+ unblock_signals();
+ else block_signals();
+
+ return ret;
+}
+
+int um_set_signals_trace(int enable)
+{
+ int ret;
+ if (signals_enabled == enable)
+ return enable;
+
+ ret = signals_enabled;
+ if (enable)
+ unblock_signals_trace();
+ else
+ block_signals_trace();
+
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
+void mark_sigio_pending(void)
+{
+ /*
+ * It would seem that this should be atomic so
+ * it isn't a read-modify-write with a signal
+ * that could happen in the middle, losing the
+ * value set by the signal.
+ *
+ * However, this function is only called when in
+ * time-travel=ext simulation mode, in which case
+ * the only signal ever pending is SIGIO, which
+ * is blocked while this can be called, and the
+ * timer signal (SIGALRM) cannot happen.
+ */
+ signals_pending |= SIGIO_MASK;
+}
+
+void block_signals_hard(void)
+{
+ signals_blocked++;
+ barrier();
+}
+
+void unblock_signals_hard(void)
+{
+ static bool unblocking;
+
+ if (!signals_blocked)
+ panic("unblocking signals while not blocked");
+
+ if (--signals_blocked)
+ return;
+ /*
+ * Must be set to 0 before we check pending so the
+ * SIGIO handler will run as normal unless we're still
+ * going to process signals_blocked_pending.
+ */
+ barrier();
+
+ /*
+ * Note that block_signals_hard()/unblock_signals_hard() can be called
+ * within the unblock_signals()/sigio_run_timetravel_handlers() below.
+ * This would still be prone to race conditions since it's actually a
+ * call _within_ e.g. vu_req_read_message(), where we observed this
+ * issue, which loops. Thus, if the inner call handles the recorded
+ * pending signals, we can get out of the inner call with the real
+ * signal hander no longer blocked, and still have a race. Thus don't
+ * handle unblocking in the inner call, if it happens, but only in
+ * the outermost call - 'unblocking' serves as an ownership for the
+ * signals_blocked_pending decrement.
+ */
+ if (unblocking)
+ return;
+ unblocking = true;
+
+ while (__atomic_load_n(&signals_blocked_pending, __ATOMIC_SEQ_CST)) {
+ if (signals_enabled) {
+ /* signals are enabled so we can touch this */
+ signals_pending |= SIGIO_MASK;
+ /*
+ * this is a bit inefficient, but that's
+ * not really important
+ */
+ block_signals();
+ unblock_signals();
+ } else {
+ /*
+ * we need to run time-travel handlers even
+ * if not enabled
+ */
+ sigio_run_timetravel_handlers();
+ }
+
+ /*
+ * The decrement of signals_blocked_pending must be atomic so
+ * that the signal handler will either happen before or after
+ * the decrement, not during a read-modify-write:
+ * - If it happens before, it can increment it and we'll
+ * decrement it and do another round in the loop.
+ * - If it happens after it'll see 0 for both signals_blocked
+ * and signals_blocked_pending and thus run the handler as
+ * usual (subject to signals_enabled, but that's unrelated.)
+ *
+ * Note that a call to unblock_signals_hard() within the calls
+ * to unblock_signals() or sigio_run_timetravel_handlers() above
+ * will do nothing due to the 'unblocking' state, so this cannot
+ * underflow as the only one decrementing will be the outermost
+ * one.
+ */
+ if (__atomic_sub_fetch(&signals_blocked_pending, 1,
+ __ATOMIC_SEQ_CST) < 0)
+ panic("signals_blocked_pending underflow");
+ }
+
+ unblocking = false;
+}
+#endif
diff --git a/arch/um/os-Linux/skas/Makefile b/arch/um/os-Linux/skas/Makefile
new file mode 100644
index 000000000..75f11989d
--- /dev/null
+++ b/arch/um/os-Linux/skas/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2002 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
+#
+
+obj-y := mem.o process.o
+
+USER_OBJS := $(obj-y)
+
+include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/um/os-Linux/skas/mem.c b/arch/um/os-Linux/skas/mem.c
new file mode 100644
index 000000000..8b9921ac3
--- /dev/null
+++ b/arch/um/os-Linux/skas/mem.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Benjamin Berg <benjamin@sipsolutions.net>
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <init.h>
+#include <as-layout.h>
+#include <mm_id.h>
+#include <os.h>
+#include <ptrace_user.h>
+#include <registers.h>
+#include <skas.h>
+#include <sysdep/ptrace.h>
+#include <sysdep/stub.h>
+#include "../internal.h"
+
+extern char __syscall_stub_start[];
+
+void syscall_stub_dump_error(struct mm_id *mm_idp)
+{
+ struct stub_data *proc_data = (void *)mm_idp->stack;
+ struct stub_syscall *sc;
+
+ if (proc_data->syscall_data_len < 0 ||
+ proc_data->syscall_data_len >= ARRAY_SIZE(proc_data->syscall_data))
+ panic("Syscall data was corrupted by stub (len is: %d, expected maximum: %d)!",
+ proc_data->syscall_data_len,
+ mm_idp->syscall_data_len);
+
+ sc = &proc_data->syscall_data[proc_data->syscall_data_len];
+
+ printk(UM_KERN_ERR "%s : length = %d, last offset = %d",
+ __func__, mm_idp->syscall_data_len,
+ proc_data->syscall_data_len);
+ printk(UM_KERN_ERR "%s : stub syscall type %d failed, return value = 0x%lx\n",
+ __func__, sc->syscall, proc_data->err);
+
+ print_hex_dump(UM_KERN_ERR, " syscall data: ", 0,
+ 16, 4, sc, sizeof(*sc), 0);
+
+ if (using_seccomp) {
+ printk(UM_KERN_ERR "%s: FD map num: %d", __func__,
+ mm_idp->syscall_fd_num);
+ print_hex_dump(UM_KERN_ERR,
+ " FD map: ", 0, 16,
+ sizeof(mm_idp->syscall_fd_map[0]),
+ mm_idp->syscall_fd_map,
+ sizeof(mm_idp->syscall_fd_map), 0);
+ }
+}
+
+static inline unsigned long *check_init_stack(struct mm_id * mm_idp,
+ unsigned long *stack)
+{
+ if (stack == NULL) {
+ stack = (unsigned long *) mm_idp->stack + 2;
+ *stack = 0;
+ }
+ return stack;
+}
+
+static unsigned long syscall_regs[MAX_REG_NR];
+
+static int __init init_syscall_regs(void)
+{
+ get_safe_registers(syscall_regs, NULL);
+
+ syscall_regs[REGS_IP_INDEX] = STUB_CODE +
+ ((unsigned long) stub_syscall_handler -
+ (unsigned long) __syscall_stub_start);
+ syscall_regs[REGS_SP_INDEX] = STUB_DATA +
+ offsetof(struct stub_data, sigstack) +
+ sizeof(((struct stub_data *) 0)->sigstack) -
+ sizeof(void *);
+
+ return 0;
+}
+
+__initcall(init_syscall_regs);
+
+static inline long do_syscall_stub(struct mm_id *mm_idp)
+{
+ struct stub_data *proc_data = (void *)mm_idp->stack;
+ int n, i;
+ int err, pid = mm_idp->pid;
+
+ /* Inform process how much we have filled in. */
+ proc_data->syscall_data_len = mm_idp->syscall_data_len;
+
+ if (using_seccomp) {
+ proc_data->restart_wait = 1;
+ wait_stub_done_seccomp(mm_idp, 0, 1);
+ } else {
+ n = ptrace_setregs(pid, syscall_regs);
+ if (n < 0) {
+ printk(UM_KERN_ERR "Registers -\n");
+ for (i = 0; i < MAX_REG_NR; i++)
+ printk(UM_KERN_ERR "\t%d\t0x%lx\n", i, syscall_regs[i]);
+ panic("%s : PTRACE_SETREGS failed, errno = %d\n",
+ __func__, -n);
+ }
+
+ err = ptrace(PTRACE_CONT, pid, 0, 0);
+ if (err)
+ panic("Failed to continue stub, pid = %d, errno = %d\n",
+ pid, errno);
+
+ wait_stub_done(pid);
+ }
+
+ /*
+ * proc_data->err will be negative if there was an (unexpected) error.
+ * In that case, syscall_data_len points to the last executed syscall,
+ * otherwise it will be zero (but we do not need to rely on that).
+ */
+ if (proc_data->err < 0) {
+ syscall_stub_dump_error(mm_idp);
+
+ /* Store error code in case someone tries to add more syscalls */
+ mm_idp->syscall_data_len = proc_data->err;
+ } else {
+ mm_idp->syscall_data_len = 0;
+ }
+
+ if (using_seccomp)
+ mm_idp->syscall_fd_num = 0;
+
+ return mm_idp->syscall_data_len;
+}
+
+int syscall_stub_flush(struct mm_id *mm_idp)
+{
+ int res;
+
+ if (mm_idp->syscall_data_len == 0)
+ return 0;
+
+ /* If an error happened already, report it and reset the state. */
+ if (mm_idp->syscall_data_len < 0) {
+ res = mm_idp->syscall_data_len;
+ mm_idp->syscall_data_len = 0;
+ return res;
+ }
+
+ res = do_syscall_stub(mm_idp);
+ mm_idp->syscall_data_len = 0;
+
+ return res;
+}
+
+struct stub_syscall *syscall_stub_alloc(struct mm_id *mm_idp)
+{
+ struct stub_syscall *sc;
+ struct stub_data *proc_data = (struct stub_data *) mm_idp->stack;
+
+ if (mm_idp->syscall_data_len > 0 &&
+ mm_idp->syscall_data_len == ARRAY_SIZE(proc_data->syscall_data))
+ do_syscall_stub(mm_idp);
+
+ if (mm_idp->syscall_data_len < 0) {
+ /* Return dummy to retain error state. */
+ sc = &proc_data->syscall_data[0];
+ } else {
+ sc = &proc_data->syscall_data[mm_idp->syscall_data_len];
+ mm_idp->syscall_data_len += 1;
+ }
+ memset(sc, 0, sizeof(*sc));
+
+ return sc;
+}
+
+static struct stub_syscall *syscall_stub_get_previous(struct mm_id *mm_idp,
+ int syscall_type,
+ unsigned long virt)
+{
+ if (mm_idp->syscall_data_len > 0) {
+ struct stub_data *proc_data = (void *) mm_idp->stack;
+ struct stub_syscall *sc;
+
+ sc = &proc_data->syscall_data[mm_idp->syscall_data_len - 1];
+
+ if (sc->syscall == syscall_type &&
+ sc->mem.addr + sc->mem.length == virt)
+ return sc;
+ }
+
+ return NULL;
+}
+
+static int get_stub_fd(struct mm_id *mm_idp, int fd)
+{
+ int i;
+
+ /* Find an FD slot (or flush and use first) */
+ if (!using_seccomp)
+ return fd;
+
+ /* Already crashed, value does not matter */
+ if (mm_idp->syscall_data_len < 0)
+ return 0;
+
+ /* Find existing FD in map if we can allocate another syscall */
+ if (mm_idp->syscall_data_len <
+ ARRAY_SIZE(((struct stub_data *)NULL)->syscall_data)) {
+ for (i = 0; i < mm_idp->syscall_fd_num; i++) {
+ if (mm_idp->syscall_fd_map[i] == fd)
+ return i;
+ }
+
+ if (mm_idp->syscall_fd_num < STUB_MAX_FDS) {
+ i = mm_idp->syscall_fd_num;
+ mm_idp->syscall_fd_map[i] = fd;
+
+ mm_idp->syscall_fd_num++;
+
+ return i;
+ }
+ }
+
+ /* FD map full or no syscall space available, continue after flush */
+ do_syscall_stub(mm_idp);
+ mm_idp->syscall_fd_map[0] = fd;
+ mm_idp->syscall_fd_num = 1;
+
+ return 0;
+}
+
+int map(struct mm_id *mm_idp, unsigned long virt, unsigned long len, int prot,
+ int phys_fd, unsigned long long offset)
+{
+ struct stub_syscall *sc;
+
+ /* Compress with previous syscall if that is possible */
+ sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MMAP, virt);
+ if (sc && sc->mem.prot == prot &&
+ sc->mem.offset == MMAP_OFFSET(offset - sc->mem.length)) {
+ int prev_fd = sc->mem.fd;
+
+ if (using_seccomp)
+ prev_fd = mm_idp->syscall_fd_map[sc->mem.fd];
+
+ if (phys_fd == prev_fd) {
+ sc->mem.length += len;
+ return 0;
+ }
+ }
+
+ phys_fd = get_stub_fd(mm_idp, phys_fd);
+
+ sc = syscall_stub_alloc(mm_idp);
+ sc->syscall = STUB_SYSCALL_MMAP;
+ sc->mem.addr = virt;
+ sc->mem.length = len;
+ sc->mem.prot = prot;
+ sc->mem.fd = phys_fd;
+ sc->mem.offset = MMAP_OFFSET(offset);
+
+ return 0;
+}
+
+int unmap(struct mm_id *mm_idp, unsigned long addr, unsigned long len)
+{
+ struct stub_syscall *sc;
+
+ /* Compress with previous syscall if that is possible */
+ sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MUNMAP, addr);
+ if (sc) {
+ sc->mem.length += len;
+ return 0;
+ }
+
+ sc = syscall_stub_alloc(mm_idp);
+ sc->syscall = STUB_SYSCALL_MUNMAP;
+ sc->mem.addr = addr;
+ sc->mem.length = len;
+
+ return 0;
+}
diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
new file mode 100644
index 000000000..d6c22f8aa
--- /dev/null
+++ b/arch/um/os-Linux/skas/process.c
@@ -0,0 +1,908 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Benjamin Berg <benjamin@sipsolutions.net>
+ * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de)
+ * Copyright (C) 2002- 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <mem_user.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <asm/unistd.h>
+#include <as-layout.h>
+#include <init.h>
+#include <kern_util.h>
+#include <mem.h>
+#include <os.h>
+#include <ptrace_user.h>
+#include <registers.h>
+#include <skas.h>
+#include <sysdep/stub.h>
+#include <sysdep/mcontext.h>
+#include <linux/futex.h>
+#include <linux/threads.h>
+#include <timetravel.h>
+#include <asm-generic/rwonce.h>
+#include "../internal.h"
+
+int is_skas_winch(int pid, int fd, void *data)
+{
+ return pid == getpgrp();
+}
+
+static const char *ptrace_reg_name(int idx)
+{
+#define R(n) case HOST_##n: return #n
+
+ switch (idx) {
+#ifdef __x86_64__
+ R(BX);
+ R(CX);
+ R(DI);
+ R(SI);
+ R(DX);
+ R(BP);
+ R(AX);
+ R(R8);
+ R(R9);
+ R(R10);
+ R(R11);
+ R(R12);
+ R(R13);
+ R(R14);
+ R(R15);
+ R(ORIG_AX);
+ R(CS);
+ R(SS);
+ R(EFLAGS);
+#elif defined(__i386__)
+ R(IP);
+ R(SP);
+ R(EFLAGS);
+ R(AX);
+ R(BX);
+ R(CX);
+ R(DX);
+ R(SI);
+ R(DI);
+ R(BP);
+ R(CS);
+ R(SS);
+ R(DS);
+ R(FS);
+ R(ES);
+ R(GS);
+ R(ORIG_AX);
+#endif
+ }
+ return "";
+}
+
+static int ptrace_dump_regs(int pid)
+{
+ unsigned long regs[MAX_REG_NR];
+ int i;
+
+ if (ptrace(PTRACE_GETREGS, pid, 0, regs) < 0)
+ return -errno;
+
+ printk(UM_KERN_ERR "Stub registers -\n");
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ const char *regname = ptrace_reg_name(i);
+
+ printk(UM_KERN_ERR "\t%s\t(%2d): %lx\n", regname, i, regs[i]);
+ }
+
+ return 0;
+}
+
+/*
+ * Signals that are OK to receive in the stub - we'll just continue it.
+ * SIGWINCH will happen when UML is inside a detached screen.
+ */
+#define STUB_SIG_MASK ((1 << SIGALRM) | (1 << SIGWINCH))
+
+/* Signals that the stub will finish with - anything else is an error */
+#define STUB_DONE_MASK (1 << SIGTRAP)
+
+void wait_stub_done(int pid)
+{
+ int n, status, err;
+
+ while (1) {
+ CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED | __WALL));
+ if ((n < 0) || !WIFSTOPPED(status))
+ goto bad_wait;
+
+ if (((1 << WSTOPSIG(status)) & STUB_SIG_MASK) == 0)
+ break;
+
+ err = ptrace(PTRACE_CONT, pid, 0, 0);
+ if (err) {
+ printk(UM_KERN_ERR "%s : continue failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+ }
+
+ if (((1 << WSTOPSIG(status)) & STUB_DONE_MASK) != 0)
+ return;
+
+bad_wait:
+ err = ptrace_dump_regs(pid);
+ if (err)
+ printk(UM_KERN_ERR "Failed to get registers from stub, errno = %d\n",
+ -err);
+ printk(UM_KERN_ERR "%s : failed to wait for SIGTRAP, pid = %d, n = %d, errno = %d, status = 0x%x\n",
+ __func__, pid, n, errno, status);
+ fatal_sigsegv();
+}
+
+void wait_stub_done_seccomp(struct mm_id *mm_idp, int running, int wait_sigsys)
+{
+ struct stub_data *data = (void *)mm_idp->stack;
+ int ret;
+
+ do {
+ const char byte = 0;
+ struct iovec iov = {
+ .iov_base = (void *)&byte,
+ .iov_len = sizeof(byte),
+ };
+ union {
+ char data[CMSG_SPACE(sizeof(mm_idp->syscall_fd_map))];
+ struct cmsghdr align;
+ } ctrl;
+ struct msghdr msgh = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+
+ if (!running) {
+ if (mm_idp->syscall_fd_num) {
+ unsigned int fds_size =
+ sizeof(int) * mm_idp->syscall_fd_num;
+ struct cmsghdr *cmsg;
+
+ msgh.msg_control = ctrl.data;
+ msgh.msg_controllen = CMSG_SPACE(fds_size);
+ cmsg = CMSG_FIRSTHDR(&msgh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(fds_size);
+ memcpy(CMSG_DATA(cmsg), mm_idp->syscall_fd_map,
+ fds_size);
+
+ CATCH_EINTR(syscall(__NR_sendmsg, mm_idp->sock,
+ &msgh, 0));
+ }
+
+ data->signal = 0;
+ data->futex = FUTEX_IN_CHILD;
+ CATCH_EINTR(syscall(__NR_futex, &data->futex,
+ FUTEX_WAKE, 1, NULL, NULL, 0));
+ }
+
+ do {
+ /*
+ * We need to check whether the child is still alive
+ * before and after the FUTEX_WAIT call. Before, in
+ * case it just died but we still updated data->futex
+ * to FUTEX_IN_CHILD. And after, in case it died while
+ * we were waiting (and SIGCHLD woke us up, see the
+ * IRQ handler in mmu.c).
+ *
+ * Either way, if PID is negative, then we have no
+ * choice but to kill the task.
+ */
+ if (__READ_ONCE(mm_idp->pid) < 0)
+ goto out_kill;
+
+ ret = syscall(__NR_futex, &data->futex,
+ FUTEX_WAIT, FUTEX_IN_CHILD,
+ NULL, NULL, 0);
+ if (ret < 0 && errno != EINTR && errno != EAGAIN) {
+ printk(UM_KERN_ERR "%s : FUTEX_WAIT failed, errno = %d\n",
+ __func__, errno);
+ goto out_kill;
+ }
+ } while (data->futex == FUTEX_IN_CHILD);
+
+ if (__READ_ONCE(mm_idp->pid) < 0)
+ goto out_kill;
+
+ running = 0;
+
+ /* We may receive a SIGALRM before SIGSYS, iterate again. */
+ } while (wait_sigsys && data->signal == SIGALRM);
+
+ if (data->mctx_offset > sizeof(data->sigstack) - sizeof(mcontext_t)) {
+ printk(UM_KERN_ERR "%s : invalid mcontext offset", __func__);
+ goto out_kill;
+ }
+
+ if (wait_sigsys && data->signal != SIGSYS) {
+ printk(UM_KERN_ERR "%s : expected SIGSYS but got %d",
+ __func__, data->signal);
+ goto out_kill;
+ }
+
+ return;
+
+out_kill:
+ printk(UM_KERN_ERR "%s : failed to wait for stub, pid = %d, errno = %d\n",
+ __func__, mm_idp->pid, errno);
+ /* This is not true inside start_userspace */
+ if (current_mm_id() == mm_idp)
+ fatal_sigsegv();
+}
+
+extern unsigned long current_stub_stack(void);
+
+static void get_skas_faultinfo(int pid, struct faultinfo *fi)
+{
+ int err;
+
+ err = ptrace(PTRACE_CONT, pid, 0, SIGSEGV);
+ if (err) {
+ printk(UM_KERN_ERR "Failed to continue stub, pid = %d, "
+ "errno = %d\n", pid, errno);
+ fatal_sigsegv();
+ }
+ wait_stub_done(pid);
+
+ /*
+ * faultinfo is prepared by the stub_segv_handler at start of
+ * the stub stack page. We just have to copy it.
+ */
+ memcpy(fi, (void *)current_stub_stack(), sizeof(*fi));
+}
+
+static void handle_trap(struct uml_pt_regs *regs)
+{
+ if ((UPT_IP(regs) >= STUB_START) && (UPT_IP(regs) < STUB_END))
+ fatal_sigsegv();
+
+ handle_syscall(regs);
+}
+
+extern char __syscall_stub_start[];
+
+static int stub_exe_fd;
+
+struct tramp_data {
+ struct stub_data *stub_data;
+ /* 0 is inherited, 1 is the kernel side */
+ int sockpair[2];
+};
+
+#ifndef CLOSE_RANGE_CLOEXEC
+#define CLOSE_RANGE_CLOEXEC (1U << 2)
+#endif
+
+static int userspace_tramp(void *data)
+{
+ struct tramp_data *tramp_data = data;
+ char *const argv[] = { "uml-userspace", NULL };
+ unsigned long long offset;
+ struct stub_init_data init_data = {
+ .seccomp = using_seccomp,
+ .stub_start = STUB_START,
+ };
+ int ret;
+
+ if (using_seccomp) {
+ init_data.signal_handler = STUB_CODE +
+ (unsigned long) stub_signal_interrupt -
+ (unsigned long) __syscall_stub_start;
+ init_data.signal_restorer = STUB_CODE +
+ (unsigned long) stub_signal_restorer -
+ (unsigned long) __syscall_stub_start;
+ } else {
+ init_data.signal_handler = STUB_CODE +
+ (unsigned long) stub_segv_handler -
+ (unsigned long) __syscall_stub_start;
+ init_data.signal_restorer = 0;
+ }
+
+ init_data.stub_code_fd = phys_mapping(uml_to_phys(__syscall_stub_start),
+ &offset);
+ init_data.stub_code_offset = MMAP_OFFSET(offset);
+
+ init_data.stub_data_fd = phys_mapping(uml_to_phys(tramp_data->stub_data),
+ &offset);
+ init_data.stub_data_offset = MMAP_OFFSET(offset);
+
+ /*
+ * Avoid leaking unneeded FDs to the stub by setting CLOEXEC on all FDs
+ * and then unsetting it on all memory related FDs.
+ * This is not strictly necessary from a safety perspective.
+ */
+ syscall(__NR_close_range, 0, ~0U, CLOSE_RANGE_CLOEXEC);
+
+ fcntl(init_data.stub_data_fd, F_SETFD, 0);
+
+ /* dup2 signaling FD/socket to STDIN */
+ if (dup2(tramp_data->sockpair[0], 0) < 0)
+ exit(3);
+ close(tramp_data->sockpair[0]);
+
+ /* Write init_data and close write side */
+ ret = write(tramp_data->sockpair[1], &init_data, sizeof(init_data));
+ close(tramp_data->sockpair[1]);
+
+ if (ret != sizeof(init_data))
+ exit(4);
+
+ /* Raw execveat for compatibility with older libc versions */
+ syscall(__NR_execveat, stub_exe_fd, (unsigned long)"",
+ (unsigned long)argv, NULL, AT_EMPTY_PATH);
+
+ exit(5);
+}
+
+extern char stub_exe_start[];
+extern char stub_exe_end[];
+
+extern char *tempdir;
+
+#define STUB_EXE_NAME_TEMPLATE "/uml-userspace-XXXXXX"
+
+#ifndef MFD_EXEC
+#define MFD_EXEC 0x0010U
+#endif
+
+static int __init init_stub_exe_fd(void)
+{
+ size_t written = 0;
+ char *tmpfile = NULL;
+
+ stub_exe_fd = memfd_create("uml-userspace",
+ MFD_EXEC | MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+ if (stub_exe_fd < 0) {
+ printk(UM_KERN_INFO "Could not create executable memfd, using temporary file!");
+
+ tmpfile = malloc(strlen(tempdir) +
+ strlen(STUB_EXE_NAME_TEMPLATE) + 1);
+ if (tmpfile == NULL)
+ panic("Failed to allocate memory for stub binary name");
+
+ strcpy(tmpfile, tempdir);
+ strcat(tmpfile, STUB_EXE_NAME_TEMPLATE);
+
+ stub_exe_fd = mkstemp(tmpfile);
+ if (stub_exe_fd < 0)
+ panic("Could not create temporary file for stub binary: %d",
+ -errno);
+ }
+
+ while (written < stub_exe_end - stub_exe_start) {
+ ssize_t res = write(stub_exe_fd, stub_exe_start + written,
+ stub_exe_end - stub_exe_start - written);
+ if (res < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (tmpfile)
+ unlink(tmpfile);
+ panic("Failed write stub binary: %d", -errno);
+ }
+
+ written += res;
+ }
+
+ if (!tmpfile) {
+ fcntl(stub_exe_fd, F_ADD_SEALS,
+ F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
+ } else {
+ if (fchmod(stub_exe_fd, 00500) < 0) {
+ unlink(tmpfile);
+ panic("Could not make stub binary executable: %d",
+ -errno);
+ }
+
+ close(stub_exe_fd);
+ stub_exe_fd = open(tmpfile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (stub_exe_fd < 0) {
+ unlink(tmpfile);
+ panic("Could not reopen stub binary: %d", -errno);
+ }
+
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ return 0;
+}
+__initcall(init_stub_exe_fd);
+
+int using_seccomp;
+
+/**
+ * start_userspace() - prepare a new userspace process
+ * @mm_id: The corresponding struct mm_id
+ *
+ * Setups a new temporary stack page that is used while userspace_tramp() runs
+ * Clones the kernel process into a new userspace process, with FDs only.
+ *
+ * Return: When positive: the process id of the new userspace process,
+ * when negative: an error number.
+ * FIXME: can PIDs become negative?!
+ */
+int start_userspace(struct mm_id *mm_id)
+{
+ struct stub_data *proc_data = (void *)mm_id->stack;
+ struct tramp_data tramp_data = {
+ .stub_data = proc_data,
+ };
+ void *stack;
+ unsigned long sp;
+ int status, n, err;
+
+ /* setup a temporary stack page */
+ stack = mmap(NULL, UM_KERN_PAGE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (stack == MAP_FAILED) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : mmap failed, errno = %d\n",
+ __func__, errno);
+ return err;
+ }
+
+ /* set stack pointer to the end of the stack page, so it can grow downwards */
+ sp = (unsigned long)stack + UM_KERN_PAGE_SIZE;
+
+ /* socket pair for init data and SECCOMP FD passing (no CLOEXEC here) */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, tramp_data.sockpair)) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : socketpair failed, errno = %d\n",
+ __func__, errno);
+ return err;
+ }
+
+ if (using_seccomp)
+ proc_data->futex = FUTEX_IN_CHILD;
+
+ mm_id->pid = clone(userspace_tramp, (void *) sp,
+ CLONE_VFORK | CLONE_VM | SIGCHLD,
+ (void *)&tramp_data);
+ if (mm_id->pid < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : clone failed, errno = %d\n",
+ __func__, errno);
+ goto out_close;
+ }
+
+ if (using_seccomp) {
+ wait_stub_done_seccomp(mm_id, 1, 1);
+ } else {
+ do {
+ CATCH_EINTR(n = waitpid(mm_id->pid, &status,
+ WUNTRACED | __WALL));
+ if (n < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : wait failed, errno = %d\n",
+ __func__, errno);
+ goto out_kill;
+ }
+ } while (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGALRM));
+
+ if (!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGSTOP)) {
+ err = -EINVAL;
+ printk(UM_KERN_ERR "%s : expected SIGSTOP, got status = %d\n",
+ __func__, status);
+ goto out_kill;
+ }
+
+ if (ptrace(PTRACE_SETOPTIONS, mm_id->pid, NULL,
+ (void *) PTRACE_O_TRACESYSGOOD) < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : PTRACE_SETOPTIONS failed, errno = %d\n",
+ __func__, errno);
+ goto out_kill;
+ }
+ }
+
+ if (munmap(stack, UM_KERN_PAGE_SIZE) < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "%s : munmap failed, errno = %d\n",
+ __func__, errno);
+ goto out_kill;
+ }
+
+ close(tramp_data.sockpair[0]);
+ if (using_seccomp)
+ mm_id->sock = tramp_data.sockpair[1];
+ else
+ close(tramp_data.sockpair[1]);
+
+ return 0;
+
+out_kill:
+ os_kill_ptraced_process(mm_id->pid, 1);
+out_close:
+ close(tramp_data.sockpair[0]);
+ close(tramp_data.sockpair[1]);
+
+ mm_id->pid = -1;
+
+ return err;
+}
+
+static int unscheduled_userspace_iterations;
+extern unsigned long tt_extra_sched_jiffies;
+
+void userspace(struct uml_pt_regs *regs)
+{
+ int err, status, op;
+ siginfo_t si_local;
+ siginfo_t *si;
+ int sig;
+
+ /* Handle any immediate reschedules or signals */
+ interrupt_end();
+
+ while (1) {
+ struct mm_id *mm_id = current_mm_id();
+
+ /*
+ * At any given time, only one CPU thread can enter the
+ * turnstile to operate on the same stub process, including
+ * executing stub system calls (mmap and munmap).
+ */
+ enter_turnstile(mm_id);
+
+ /*
+ * When we are in time-travel mode, userspace can theoretically
+ * do a *lot* of work without being scheduled. The problem with
+ * this is that it will prevent kernel bookkeeping (primarily
+ * the RCU) from running and this can for example cause OOM
+ * situations.
+ *
+ * This code accounts a jiffie against the scheduling clock
+ * after the defined userspace iterations in the same thread.
+ * By doing so the situation is effectively prevented.
+ */
+ if (time_travel_mode == TT_MODE_INFCPU ||
+ time_travel_mode == TT_MODE_EXTERNAL) {
+#ifdef CONFIG_UML_MAX_USERSPACE_ITERATIONS
+ if (CONFIG_UML_MAX_USERSPACE_ITERATIONS &&
+ unscheduled_userspace_iterations++ >
+ CONFIG_UML_MAX_USERSPACE_ITERATIONS) {
+ tt_extra_sched_jiffies += 1;
+ unscheduled_userspace_iterations = 0;
+ }
+#endif
+ }
+
+ time_travel_print_bc_msg();
+
+ current_mm_sync();
+
+ if (using_seccomp) {
+ struct stub_data *proc_data = (void *) mm_id->stack;
+
+ err = set_stub_state(regs, proc_data, singlestepping());
+ if (err) {
+ printk(UM_KERN_ERR "%s - failed to set regs: %d",
+ __func__, err);
+ fatal_sigsegv();
+ }
+
+ /* Must have been reset by the syscall caller */
+ if (proc_data->restart_wait != 0)
+ panic("Programming error: Flag to only run syscalls in child was not cleared!");
+
+ /* Mark pending syscalls for flushing */
+ proc_data->syscall_data_len = mm_id->syscall_data_len;
+
+ wait_stub_done_seccomp(mm_id, 0, 0);
+
+ sig = proc_data->signal;
+
+ if (sig == SIGTRAP && proc_data->err != 0) {
+ printk(UM_KERN_ERR "%s - Error flushing stub syscalls",
+ __func__);
+ syscall_stub_dump_error(mm_id);
+ mm_id->syscall_data_len = proc_data->err;
+ fatal_sigsegv();
+ }
+
+ mm_id->syscall_data_len = 0;
+ mm_id->syscall_fd_num = 0;
+
+ err = get_stub_state(regs, proc_data, NULL);
+ if (err) {
+ printk(UM_KERN_ERR "%s - failed to get regs: %d",
+ __func__, err);
+ fatal_sigsegv();
+ }
+
+ if (proc_data->si_offset > sizeof(proc_data->sigstack) - sizeof(*si))
+ panic("%s - Invalid siginfo offset from child", __func__);
+
+ si = &si_local;
+ memcpy(si, &proc_data->sigstack[proc_data->si_offset], sizeof(*si));
+
+ regs->is_user = 1;
+
+ /* Fill in ORIG_RAX and extract fault information */
+ PT_SYSCALL_NR(regs->gp) = si->si_syscall;
+ if (sig == SIGSEGV) {
+ mcontext_t *mcontext = (void *)&proc_data->sigstack[proc_data->mctx_offset];
+
+ GET_FAULTINFO_FROM_MC(regs->faultinfo, mcontext);
+ }
+ } else {
+ int pid = mm_id->pid;
+
+ /* Flush out any pending syscalls */
+ err = syscall_stub_flush(mm_id);
+ if (err) {
+ if (err == -ENOMEM)
+ report_enomem();
+
+ printk(UM_KERN_ERR "%s - Error flushing stub syscalls: %d",
+ __func__, -err);
+ fatal_sigsegv();
+ }
+
+ /*
+ * This can legitimately fail if the process loads a
+ * bogus value into a segment register. It will
+ * segfault and PTRACE_GETREGS will read that value
+ * out of the process. However, PTRACE_SETREGS will
+ * fail. In this case, there is nothing to do but
+ * just kill the process.
+ */
+ if (ptrace(PTRACE_SETREGS, pid, 0, regs->gp)) {
+ printk(UM_KERN_ERR "%s - ptrace set regs failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+
+ if (put_fp_registers(pid, regs->fp)) {
+ printk(UM_KERN_ERR "%s - ptrace set fp regs failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+
+ if (singlestepping())
+ op = PTRACE_SYSEMU_SINGLESTEP;
+ else
+ op = PTRACE_SYSEMU;
+
+ if (ptrace(op, pid, 0, 0)) {
+ printk(UM_KERN_ERR "%s - ptrace continue failed, op = %d, errno = %d\n",
+ __func__, op, errno);
+ fatal_sigsegv();
+ }
+
+ CATCH_EINTR(err = waitpid(pid, &status, WUNTRACED | __WALL));
+ if (err < 0) {
+ printk(UM_KERN_ERR "%s - wait failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+
+ regs->is_user = 1;
+ if (ptrace(PTRACE_GETREGS, pid, 0, regs->gp)) {
+ printk(UM_KERN_ERR "%s - PTRACE_GETREGS failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+
+ if (get_fp_registers(pid, regs->fp)) {
+ printk(UM_KERN_ERR "%s - get_fp_registers failed, errno = %d\n",
+ __func__, errno);
+ fatal_sigsegv();
+ }
+
+ if (WIFSTOPPED(status)) {
+ sig = WSTOPSIG(status);
+
+ /*
+ * These signal handlers need the si argument
+ * and SIGSEGV needs the faultinfo.
+ * The SIGIO and SIGALARM handlers which constitute
+ * the majority of invocations, do not use it.
+ */
+ switch (sig) {
+ case SIGSEGV:
+ get_skas_faultinfo(pid,
+ &regs->faultinfo);
+ fallthrough;
+ case SIGTRAP:
+ case SIGILL:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGWINCH:
+ ptrace(PTRACE_GETSIGINFO, pid, 0,
+ (struct siginfo *)&si_local);
+ si = &si_local;
+ break;
+ default:
+ si = NULL;
+ break;
+ }
+ } else {
+ sig = 0;
+ }
+ }
+
+ exit_turnstile(mm_id);
+
+ UPT_SYSCALL_NR(regs) = -1; /* Assume: It's not a syscall */
+
+ if (sig) {
+ switch (sig) {
+ case SIGSEGV:
+ if (using_seccomp || PTRACE_FULL_FAULTINFO)
+ (*sig_info[SIGSEGV])(SIGSEGV,
+ (struct siginfo *)si,
+ regs, NULL);
+ else
+ segv(regs->faultinfo, 0, 1, NULL, NULL);
+
+ break;
+ case SIGSYS:
+ handle_syscall(regs);
+ break;
+ case SIGTRAP + 0x80:
+ handle_trap(regs);
+ break;
+ case SIGTRAP:
+ relay_signal(SIGTRAP, (struct siginfo *)si, regs, NULL);
+ break;
+ case SIGALRM:
+ break;
+ case SIGIO:
+ case SIGILL:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGWINCH:
+ block_signals_trace();
+ (*sig_info[sig])(sig, (struct siginfo *)si, regs, NULL);
+ unblock_signals_trace();
+ break;
+ default:
+ printk(UM_KERN_ERR "%s - child stopped with signal %d\n",
+ __func__, sig);
+ fatal_sigsegv();
+ }
+ interrupt_end();
+
+ /* Avoid -ERESTARTSYS handling in host */
+ if (PT_SYSCALL_NR_OFFSET != PT_SYSCALL_RET_OFFSET)
+ PT_SYSCALL_NR(regs->gp) = -1;
+ }
+ }
+}
+
+void new_thread(void *stack, jmp_buf *buf, void (*handler)(void))
+{
+ (*buf)[0].JB_IP = (unsigned long) handler;
+ (*buf)[0].JB_SP = (unsigned long) stack + UM_THREAD_SIZE -
+ sizeof(void *);
+}
+
+#define INIT_JMP_NEW_THREAD 0
+#define INIT_JMP_CALLBACK 1
+#define INIT_JMP_HALT 2
+#define INIT_JMP_REBOOT 3
+
+void switch_threads(jmp_buf *me, jmp_buf *you)
+{
+ unscheduled_userspace_iterations = 0;
+
+ if (UML_SETJMP(me) == 0)
+ UML_LONGJMP(you, 1);
+}
+
+static jmp_buf initial_jmpbuf;
+
+static __thread void (*cb_proc)(void *arg);
+static __thread void *cb_arg;
+static __thread jmp_buf *cb_back;
+
+int start_idle_thread(void *stack, jmp_buf *switch_buf)
+{
+ int n;
+
+ set_handler(SIGWINCH);
+
+ /*
+ * Can't use UML_SETJMP or UML_LONGJMP here because they save
+ * and restore signals, with the possible side-effect of
+ * trying to handle any signals which came when they were
+ * blocked, which can't be done on this stack.
+ * Signals must be blocked when jumping back here and restored
+ * after returning to the jumper.
+ */
+ n = setjmp(initial_jmpbuf);
+ switch (n) {
+ case INIT_JMP_NEW_THREAD:
+ (*switch_buf)[0].JB_IP = (unsigned long) uml_finishsetup;
+ (*switch_buf)[0].JB_SP = (unsigned long) stack +
+ UM_THREAD_SIZE - sizeof(void *);
+ break;
+ case INIT_JMP_CALLBACK:
+ (*cb_proc)(cb_arg);
+ longjmp(*cb_back, 1);
+ break;
+ case INIT_JMP_HALT:
+ kmalloc_ok = 0;
+ return 0;
+ case INIT_JMP_REBOOT:
+ kmalloc_ok = 0;
+ return 1;
+ default:
+ printk(UM_KERN_ERR "Bad sigsetjmp return in %s - %d\n",
+ __func__, n);
+ fatal_sigsegv();
+ }
+ longjmp(*switch_buf, 1);
+
+ /* unreachable */
+ printk(UM_KERN_ERR "impossible long jump!");
+ fatal_sigsegv();
+ return 0;
+}
+
+void initial_thread_cb_skas(void (*proc)(void *), void *arg)
+{
+ jmp_buf here;
+
+ cb_proc = proc;
+ cb_arg = arg;
+ cb_back = &here;
+
+ initial_jmpbuf_lock();
+ if (UML_SETJMP(&here) == 0)
+ UML_LONGJMP(&initial_jmpbuf, INIT_JMP_CALLBACK);
+ initial_jmpbuf_unlock();
+
+ cb_proc = NULL;
+ cb_arg = NULL;
+ cb_back = NULL;
+}
+
+void halt_skas(void)
+{
+ initial_jmpbuf_lock();
+ UML_LONGJMP(&initial_jmpbuf, INIT_JMP_HALT);
+ /* unreachable */
+}
+
+static bool noreboot;
+
+static int __init noreboot_cmd_param(char *str, int *add)
+{
+ *add = 0;
+ noreboot = true;
+ return 0;
+}
+
+__uml_setup("noreboot", noreboot_cmd_param,
+"noreboot\n"
+" Rather than rebooting, exit always, akin to QEMU's -no-reboot option.\n"
+" This is useful if you're using CONFIG_PANIC_TIMEOUT in order to catch\n"
+" crashes in CI\n\n");
+
+void reboot_skas(void)
+{
+ initial_jmpbuf_lock();
+ UML_LONGJMP(&initial_jmpbuf, noreboot ? INIT_JMP_HALT : INIT_JMP_REBOOT);
+ /* unreachable */
+}
diff --git a/arch/um/os-Linux/smp.c b/arch/um/os-Linux/smp.c
new file mode 100644
index 000000000..18d3858a7
--- /dev/null
+++ b/arch/um/os-Linux/smp.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 Ant Group
+ * Author: Tiwei Bie <tiwei.btw@antgroup.com>
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <kern_util.h>
+#include <um_malloc.h>
+#include <init.h>
+#include <os.h>
+#include <smp.h>
+#include "internal.h"
+
+struct cpu_thread_data {
+ int cpu;
+ sigset_t sigset;
+};
+
+static __thread int __curr_cpu;
+
+int uml_curr_cpu(void)
+{
+ return __curr_cpu;
+}
+
+static pthread_t cpu_threads[CONFIG_NR_CPUS];
+
+static void *cpu_thread(void *arg)
+{
+ struct cpu_thread_data *data = arg;
+
+ __curr_cpu = data->cpu;
+
+ uml_start_secondary(data);
+
+ return NULL;
+}
+
+int os_start_cpu_thread(int cpu)
+{
+ struct cpu_thread_data *data;
+ sigset_t sigset, oset;
+ int err;
+
+ data = uml_kmalloc(sizeof(*data), UM_GFP_ATOMIC);
+ if (!data)
+ return -ENOMEM;
+
+ sigfillset(&sigset);
+ if (sigprocmask(SIG_SETMASK, &sigset, &oset) < 0) {
+ err = errno;
+ goto err;
+ }
+
+ data->cpu = cpu;
+ data->sigset = oset;
+
+ err = pthread_create(&cpu_threads[cpu], NULL, cpu_thread, data);
+ if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0)
+ panic("Failed to restore the signal mask, errno = %d", errno);
+ if (err != 0)
+ goto err;
+
+ return 0;
+
+err:
+ kfree(data);
+ return -err;
+}
+
+void os_start_secondary(void *arg, jmp_buf *switch_buf)
+{
+ struct cpu_thread_data *data = arg;
+
+ sigaddset(&data->sigset, IPI_SIGNAL);
+ sigaddset(&data->sigset, SIGIO);
+
+ if (sigprocmask(SIG_SETMASK, &data->sigset, NULL) < 0)
+ panic("Failed to restore the signal mask, errno = %d", errno);
+
+ kfree(data);
+ longjmp(*switch_buf, 1);
+
+ /* unreachable */
+ printk(UM_KERN_ERR "impossible long jump!");
+ fatal_sigsegv();
+}
+
+int os_send_ipi(int cpu, int vector)
+{
+ union sigval value = { .sival_int = vector };
+
+ return pthread_sigqueue(cpu_threads[cpu], IPI_SIGNAL, value);
+}
+
+static void __local_ipi_set(int enable)
+{
+ sigset_t sigset;
+
+ sigemptyset(&sigset);
+ sigaddset(&sigset, IPI_SIGNAL);
+
+ if (sigprocmask(enable ? SIG_UNBLOCK : SIG_BLOCK, &sigset, NULL) < 0)
+ panic("%s: sigprocmask failed, errno = %d", __func__, errno);
+}
+
+void os_local_ipi_enable(void)
+{
+ __local_ipi_set(1);
+}
+
+void os_local_ipi_disable(void)
+{
+ __local_ipi_set(0);
+}
+
+static void ipi_sig_handler(int sig, siginfo_t *si, void *uc)
+{
+ int save_errno = errno;
+
+ signals_enabled = 0;
+ um_trace_signals_off();
+
+ uml_ipi_handler(si->si_value.sival_int);
+
+ um_trace_signals_on();
+ signals_enabled = 1;
+
+ errno = save_errno;
+}
+
+void __init os_init_smp(void)
+{
+ struct sigaction action = {
+ .sa_sigaction = ipi_sig_handler,
+ .sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART,
+ };
+
+ sigfillset(&action.sa_mask);
+
+ if (sigaction(IPI_SIGNAL, &action, NULL) < 0)
+ panic("%s: sigaction failed, errno = %d", __func__, errno);
+
+ cpu_threads[0] = pthread_self();
+}
diff --git a/arch/um/os-Linux/start_up.c b/arch/um/os-Linux/start_up.c
new file mode 100644
index 000000000..054ac03bb
--- /dev/null
+++ b/arch/um/os-Linux/start_up.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Benjamin Berg <benjamin@sipsolutions.net>
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <asm/ldt.h>
+#include <asm/unistd.h>
+#include <init.h>
+#include <os.h>
+#include <smp.h>
+#include <kern_util.h>
+#include <mem_user.h>
+#include <ptrace_user.h>
+#include <stdbool.h>
+#include <stub-data.h>
+#include <sys/prctl.h>
+#include <linux/seccomp.h>
+#include <linux/filter.h>
+#include <sysdep/mcontext.h>
+#include <sysdep/stub.h>
+#include <registers.h>
+#include <skas.h>
+#include "internal.h"
+
+static void ptrace_child(void)
+{
+ int ret;
+ /* Calling os_getpid because some libcs cached getpid incorrectly */
+ int pid = os_getpid(), ppid = getppid();
+ int sc_result;
+
+ if (change_sig(SIGWINCH, 0) < 0 ||
+ ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
+ perror("ptrace");
+ kill(pid, SIGKILL);
+ }
+ kill(pid, SIGSTOP);
+
+ /*
+ * This syscall will be intercepted by the parent. Don't call more than
+ * once, please.
+ */
+ sc_result = os_getpid();
+
+ if (sc_result == pid)
+ /* Nothing modified by the parent, we are running normally. */
+ ret = 1;
+ else if (sc_result == ppid)
+ /*
+ * Expected in check_ptrace and check_sysemu when they succeed
+ * in modifying the stack frame
+ */
+ ret = 0;
+ else
+ /* Serious trouble! This could be caused by a bug in host 2.6
+ * SKAS3/2.6 patch before release -V6, together with a bug in
+ * the UML code itself.
+ */
+ ret = 2;
+
+ exit(ret);
+}
+
+static void fatal_perror(const char *str)
+{
+ perror(str);
+ exit(1);
+}
+
+static void fatal(char *fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ va_end(list);
+
+ exit(1);
+}
+
+static void non_fatal(char *fmt, ...)
+{
+ va_list list;
+
+ va_start(list, fmt);
+ vfprintf(stderr, fmt, list);
+ va_end(list);
+}
+
+static int start_ptraced_child(void)
+{
+ int pid, n, status;
+
+ fflush(stdout);
+
+ pid = fork();
+ if (pid == 0)
+ ptrace_child();
+ else if (pid < 0)
+ fatal_perror("start_ptraced_child : fork failed");
+
+ CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED));
+ if (n < 0)
+ fatal_perror("check_ptrace : waitpid failed");
+ if (!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGSTOP))
+ fatal("check_ptrace : expected SIGSTOP, got status = %d",
+ status);
+
+ return pid;
+}
+
+static void stop_ptraced_child(int pid, int exitcode)
+{
+ int status, n;
+
+ if (ptrace(PTRACE_CONT, pid, 0, 0) < 0)
+ fatal_perror("stop_ptraced_child : ptrace failed");
+
+ CATCH_EINTR(n = waitpid(pid, &status, 0));
+ if (!WIFEXITED(status) || (WEXITSTATUS(status) != exitcode)) {
+ int exit_with = WEXITSTATUS(status);
+ fatal("stop_ptraced_child : child exited with exitcode %d, "
+ "while expecting %d; status 0x%x\n", exit_with,
+ exitcode, status);
+ }
+}
+
+static void __init check_sysemu(void)
+{
+ int pid, n, status, count=0;
+
+ os_info("Checking syscall emulation for ptrace...");
+ pid = start_ptraced_child();
+
+ if ((ptrace(PTRACE_SETOPTIONS, pid, 0,
+ (void *) PTRACE_O_TRACESYSGOOD) < 0))
+ fatal_perror("check_sysemu: PTRACE_SETOPTIONS failed");
+
+ while (1) {
+ count++;
+ if (ptrace(PTRACE_SYSEMU_SINGLESTEP, pid, 0, 0) < 0)
+ goto fail;
+ CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED));
+ if (n < 0)
+ fatal_perror("check_sysemu: wait failed");
+
+ if (WIFSTOPPED(status) &&
+ (WSTOPSIG(status) == (SIGTRAP|0x80))) {
+ if (!count) {
+ non_fatal("check_sysemu: SYSEMU_SINGLESTEP "
+ "doesn't singlestep");
+ goto fail;
+ }
+ n = ptrace(PTRACE_POKEUSER, pid, PT_SYSCALL_RET_OFFSET,
+ os_getpid());
+ if (n < 0)
+ fatal_perror("check_sysemu : failed to modify "
+ "system call return");
+ break;
+ }
+ else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP))
+ count++;
+ else {
+ non_fatal("check_sysemu: expected SIGTRAP or "
+ "(SIGTRAP | 0x80), got status = %d\n",
+ status);
+ goto fail;
+ }
+ }
+ stop_ptraced_child(pid, 0);
+
+ os_info("OK\n");
+
+ return;
+
+fail:
+ stop_ptraced_child(pid, 1);
+ fatal("missing\n");
+}
+
+static void __init check_ptrace(void)
+{
+ int pid, syscall, n, status;
+
+ os_info("Checking that ptrace can change system call numbers...");
+ pid = start_ptraced_child();
+
+ if ((ptrace(PTRACE_SETOPTIONS, pid, 0,
+ (void *) PTRACE_O_TRACESYSGOOD) < 0))
+ fatal_perror("check_ptrace: PTRACE_SETOPTIONS failed");
+
+ while (1) {
+ if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
+ fatal_perror("check_ptrace : ptrace failed");
+
+ CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED));
+ if (n < 0)
+ fatal_perror("check_ptrace : wait failed");
+
+ if (!WIFSTOPPED(status) ||
+ (WSTOPSIG(status) != (SIGTRAP | 0x80)))
+ fatal("check_ptrace : expected (SIGTRAP|0x80), "
+ "got status = %d", status);
+
+ syscall = ptrace(PTRACE_PEEKUSER, pid, PT_SYSCALL_NR_OFFSET,
+ 0);
+ if (syscall == __NR_getpid) {
+ n = ptrace(PTRACE_POKEUSER, pid, PT_SYSCALL_NR_OFFSET,
+ __NR_getppid);
+ if (n < 0)
+ fatal_perror("check_ptrace : failed to modify "
+ "system call");
+ break;
+ }
+ }
+ stop_ptraced_child(pid, 0);
+ os_info("OK\n");
+ check_sysemu();
+}
+
+extern unsigned long host_fp_size;
+extern unsigned long exec_regs[MAX_REG_NR];
+extern unsigned long *exec_fp_regs;
+
+__initdata static struct stub_data *seccomp_test_stub_data;
+
+static void __init sigsys_handler(int sig, siginfo_t *info, void *p)
+{
+ ucontext_t *uc = p;
+
+ /* Stow away the location of the mcontext in the stack */
+ seccomp_test_stub_data->mctx_offset = (unsigned long)&uc->uc_mcontext -
+ (unsigned long)&seccomp_test_stub_data->sigstack[0];
+
+ /* Prevent libc from clearing memory (mctx_offset in particular) */
+ syscall(__NR_exit, 0);
+}
+
+static int __init seccomp_helper(void *data)
+{
+ static struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ offsetof(struct seccomp_data, nr)),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clock_nanosleep, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP),
+ };
+ static struct sock_fprog prog = {
+ .len = ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+ struct sigaction sa;
+
+ /* close_range is needed for the stub */
+ if (stub_syscall3(__NR_close_range, 1, ~0U, 0))
+ exit(1);
+
+ set_sigstack(seccomp_test_stub_data->sigstack,
+ sizeof(seccomp_test_stub_data->sigstack));
+
+ sa.sa_flags = SA_ONSTACK | SA_NODEFER | SA_SIGINFO;
+ sa.sa_sigaction = (void *) sigsys_handler;
+ sa.sa_restorer = NULL;
+ if (sigaction(SIGSYS, &sa, NULL) < 0)
+ exit(2);
+
+ prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
+ SECCOMP_FILTER_FLAG_TSYNC, &prog) != 0)
+ exit(3);
+
+ sleep(0);
+
+ /* Never reached. */
+ _exit(4);
+}
+
+static bool __init init_seccomp(void)
+{
+ int pid;
+ int status;
+ int n;
+ unsigned long sp;
+
+ /*
+ * We check that we can install a seccomp filter and then exit(0)
+ * from a trapped syscall.
+ *
+ * Note that we cannot verify that no seccomp filter already exists
+ * for a syscall that results in the process/thread to be killed.
+ */
+
+ os_info("Checking that seccomp filters can be installed...");
+
+ seccomp_test_stub_data = mmap(0, sizeof(*seccomp_test_stub_data),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANON, 0, 0);
+
+ /* Use the syscall data area as stack, we just need something */
+ sp = (unsigned long)&seccomp_test_stub_data->syscall_data +
+ sizeof(seccomp_test_stub_data->syscall_data) -
+ sizeof(void *);
+ pid = clone(seccomp_helper, (void *)sp, CLONE_VFORK | CLONE_VM, NULL);
+
+ if (pid < 0)
+ fatal_perror("check_seccomp : clone failed");
+
+ CATCH_EINTR(n = waitpid(pid, &status, __WCLONE));
+ if (n < 0)
+ fatal_perror("check_seccomp : waitpid failed");
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ struct uml_pt_regs *regs;
+ unsigned long fp_size;
+ int r;
+
+ /* Fill in the host_fp_size from the mcontext. */
+ regs = calloc(1, sizeof(struct uml_pt_regs));
+ get_stub_state(regs, seccomp_test_stub_data, &fp_size);
+ host_fp_size = fp_size;
+ free(regs);
+
+ /* Repeat with the correct size */
+ regs = calloc(1, sizeof(struct uml_pt_regs) + host_fp_size);
+ r = get_stub_state(regs, seccomp_test_stub_data, NULL);
+
+ /* Store as the default startup registers */
+ exec_fp_regs = malloc(host_fp_size);
+ memcpy(exec_regs, regs->gp, sizeof(exec_regs));
+ memcpy(exec_fp_regs, regs->fp, host_fp_size);
+
+ munmap(seccomp_test_stub_data, sizeof(*seccomp_test_stub_data));
+
+ free(regs);
+
+ if (r) {
+ os_info("failed to fetch registers: %d\n", r);
+ return false;
+ }
+
+ os_info("OK\n");
+ return true;
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 2)
+ os_info("missing\n");
+ else
+ os_info("error\n");
+
+ munmap(seccomp_test_stub_data, sizeof(*seccomp_test_stub_data));
+ return false;
+}
+
+
+static void __init check_coredump_limit(void)
+{
+ struct rlimit lim;
+ int err = getrlimit(RLIMIT_CORE, &lim);
+
+ if (err) {
+ perror("Getting core dump limit");
+ return;
+ }
+
+ os_info("Core dump limits :\n\tsoft - ");
+ if (lim.rlim_cur == RLIM_INFINITY)
+ os_info("NONE\n");
+ else
+ os_info("%llu\n", (unsigned long long)lim.rlim_cur);
+
+ os_info("\thard - ");
+ if (lim.rlim_max == RLIM_INFINITY)
+ os_info("NONE\n");
+ else
+ os_info("%llu\n", (unsigned long long)lim.rlim_max);
+}
+
+void __init get_host_cpu_features(
+ void (*flags_helper_func)(char *line),
+ void (*cache_helper_func)(char *line))
+{
+ FILE *cpuinfo;
+ char *line = NULL;
+ size_t len = 0;
+ int done_parsing = 0;
+
+ cpuinfo = fopen("/proc/cpuinfo", "r");
+ if (cpuinfo == NULL) {
+ os_info("Failed to get host CPU features\n");
+ } else {
+ while ((getline(&line, &len, cpuinfo)) != -1) {
+ if (strstr(line, "flags")) {
+ flags_helper_func(line);
+ done_parsing++;
+ }
+ if (strstr(line, "cache_alignment")) {
+ cache_helper_func(line);
+ done_parsing++;
+ }
+ free(line);
+ line = NULL;
+ if (done_parsing > 1)
+ break;
+ }
+ fclose(cpuinfo);
+ }
+}
+
+static int seccomp_config __initdata;
+
+static int __init uml_seccomp_config(char *line, int *add)
+{
+ *add = 0;
+
+ if (strcmp(line, "off") == 0)
+ seccomp_config = 0;
+ else if (strcmp(line, "auto") == 0)
+ seccomp_config = 1;
+ else if (strcmp(line, "on") == 0)
+ seccomp_config = 2;
+ else
+ fatal("Invalid seccomp option '%s', expected on/auto/off\n",
+ line);
+
+ return 0;
+}
+
+__uml_setup("seccomp=", uml_seccomp_config,
+"seccomp=<on/auto/off>\n"
+" Configure whether or not SECCOMP is used. With SECCOMP, userspace\n"
+" processes work collaboratively with the kernel instead of being\n"
+" traced using ptrace. All syscalls from the application are caught and\n"
+" redirected using a signal. This signal handler in turn is permitted to\n"
+" do the selected set of syscalls to communicate with the UML kernel and\n"
+" do the required memory management.\n"
+"\n"
+" This method is overall faster than the ptrace based userspace, primarily\n"
+" because it reduces the number of context switches for (minor) page faults.\n"
+"\n"
+" However, the SECCOMP filter is not (yet) restrictive enough to prevent\n"
+" userspace from reading and writing all physical memory. Userspace\n"
+" processes could also trick the stub into disabling SIGALRM which\n"
+" prevents it from being interrupted for scheduling purposes.\n"
+"\n"
+" This is insecure and should only be used with a trusted userspace\n\n"
+);
+
+void __init os_early_checks(void)
+{
+ int pid;
+
+ /* Print out the core dump limits early */
+ check_coredump_limit();
+
+ /* Need to check this early because mmapping happens before the
+ * kernel is running.
+ */
+ check_tmpexec();
+
+ if (seccomp_config) {
+ if (init_seccomp()) {
+ using_seccomp = 1;
+ return;
+ }
+
+ if (seccomp_config == 2)
+ fatal("SECCOMP userspace requested but not functional!\n");
+ }
+
+ if (uml_ncpus > 1)
+ fatal("SMP is not supported with PTRACE userspace.\n");
+
+ using_seccomp = 0;
+ check_ptrace();
+
+ pid = start_ptraced_child();
+ if (init_pid_registers(pid))
+ fatal("Failed to initialize default registers");
+ stop_ptraced_child(pid, 1);
+}
diff --git a/arch/um/os-Linux/time.c b/arch/um/os-Linux/time.c
new file mode 100644
index 000000000..13ebc8691
--- /dev/null
+++ b/arch/um/os-Linux/time.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Anton Ivanov (aivanov@{brocade.com,kot-begemot.co.uk})
+ * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de)
+ * Copyright (C) 2012-2014 Cisco Systems
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike{addtoit,linux.intel}.com)
+ */
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/signalfd.h>
+#include <sys/time.h>
+#include <kern_util.h>
+#include <os.h>
+#include <smp.h>
+#include <string.h>
+#include "internal.h"
+
+static timer_t event_high_res_timer[CONFIG_NR_CPUS] = { 0 };
+
+static inline long long timespec_to_ns(const struct timespec *ts)
+{
+ return ((long long) ts->tv_sec * UM_NSEC_PER_SEC) + ts->tv_nsec;
+}
+
+long long os_persistent_clock_emulation(void)
+{
+ struct timespec realtime_tp;
+
+ clock_gettime(CLOCK_REALTIME, &realtime_tp);
+ return timespec_to_ns(&realtime_tp);
+}
+
+#ifndef sigev_notify_thread_id
+#define sigev_notify_thread_id _sigev_un._tid
+#endif
+
+/**
+ * os_timer_create() - create an new posix (interval) timer
+ */
+int os_timer_create(void)
+{
+ int cpu = uml_curr_cpu();
+ timer_t *t = &event_high_res_timer[cpu];
+ struct sigevent sev = {
+ .sigev_notify = SIGEV_THREAD_ID,
+ .sigev_signo = SIGALRM,
+ .sigev_value.sival_ptr = t,
+ .sigev_notify_thread_id = gettid(),
+ };
+
+ if (timer_create(CLOCK_MONOTONIC, &sev, t) == -1)
+ return -1;
+
+ return 0;
+}
+
+int os_timer_set_interval(int cpu, unsigned long long nsecs)
+{
+ struct itimerspec its;
+
+ its.it_value.tv_sec = nsecs / UM_NSEC_PER_SEC;
+ its.it_value.tv_nsec = nsecs % UM_NSEC_PER_SEC;
+
+ its.it_interval.tv_sec = nsecs / UM_NSEC_PER_SEC;
+ its.it_interval.tv_nsec = nsecs % UM_NSEC_PER_SEC;
+
+ if (timer_settime(event_high_res_timer[cpu], 0, &its, NULL) == -1)
+ return -errno;
+
+ return 0;
+}
+
+int os_timer_one_shot(int cpu, unsigned long long nsecs)
+{
+ struct itimerspec its = {
+ .it_value.tv_sec = nsecs / UM_NSEC_PER_SEC,
+ .it_value.tv_nsec = nsecs % UM_NSEC_PER_SEC,
+
+ .it_interval.tv_sec = 0,
+ .it_interval.tv_nsec = 0, // we cheat here
+ };
+
+ timer_settime(event_high_res_timer[cpu], 0, &its, NULL);
+ return 0;
+}
+
+/**
+ * os_timer_disable() - disable the posix (interval) timer
+ * @cpu: the CPU for which the timer is to be disabled
+ */
+void os_timer_disable(int cpu)
+{
+ struct itimerspec its;
+
+ memset(&its, 0, sizeof(struct itimerspec));
+ timer_settime(event_high_res_timer[cpu], 0, &its, NULL);
+}
+
+long long os_nsecs(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC,&ts);
+ return timespec_to_ns(&ts);
+}
+
+static __thread int wake_signals;
+
+void os_idle_prepare(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGALRM);
+ sigaddset(&set, IPI_SIGNAL);
+
+ /*
+ * We need to use signalfd rather than sigsuspend in idle sleep
+ * because the IPI signal is a real-time signal that carries data,
+ * and unlike handling SIGALRM, we cannot simply flag it in
+ * signals_pending.
+ */
+ wake_signals = signalfd(-1, &set, SFD_CLOEXEC);
+ if (wake_signals < 0)
+ panic("Failed to create signal FD, errno = %d", errno);
+}
+
+/**
+ * os_idle_sleep() - sleep until interrupted
+ */
+void os_idle_sleep(void)
+{
+ sigset_t set;
+
+ /*
+ * Block SIGALRM while performing the need_resched check.
+ * Note that, because IRQs are disabled, the IPI signal is
+ * already blocked.
+ */
+ sigemptyset(&set);
+ sigaddset(&set, SIGALRM);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+
+ /*
+ * Because disabling IRQs does not block SIGALRM, it is also
+ * necessary to check for any pending timer alarms.
+ */
+ if (!uml_need_resched() && !timer_alarm_pending())
+ os_poll(1, &wake_signals);
+
+ /* Restore the signal mask. */
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+}
diff --git a/arch/um/os-Linux/tty.c b/arch/um/os-Linux/tty.c
new file mode 100644
index 000000000..f784db83e
--- /dev/null
+++ b/arch/um/os-Linux/tty.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kern_util.h>
+#include <os.h>
+
+struct grantpt_info {
+ int fd;
+ int res;
+ int err;
+};
+
+static void grantpt_cb(void *arg)
+{
+ struct grantpt_info *info = arg;
+
+ info->res = grantpt(info->fd);
+ info->err = errno;
+}
+
+int get_pty(void)
+{
+ struct grantpt_info info;
+ int fd, err;
+
+ fd = open("/dev/ptmx", O_RDWR);
+ if (fd < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "get_pty : Couldn't open /dev/ptmx - "
+ "err = %d\n", errno);
+ return err;
+ }
+
+ info.fd = fd;
+ initial_thread_cb(grantpt_cb, &info);
+
+ if (info.res < 0) {
+ err = -info.err;
+ printk(UM_KERN_ERR "get_pty : Couldn't grant pty - "
+ "errno = %d\n", -info.err);
+ goto out;
+ }
+
+ if (unlockpt(fd) < 0) {
+ err = -errno;
+ printk(UM_KERN_ERR "get_pty : Couldn't unlock pty - "
+ "errno = %d\n", errno);
+ goto out;
+ }
+ return fd;
+out:
+ close(fd);
+ return err;
+}
diff --git a/arch/um/os-Linux/umid.c b/arch/um/os-Linux/umid.c
new file mode 100644
index 000000000..6b488be2c
--- /dev/null
+++ b/arch/um/os-Linux/umid.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <init.h>
+#include <os.h>
+
+#define UML_DIR "~/.uml/"
+
+#define UMID_LEN 64
+
+/* Changed by set_umid, which is run early in boot */
+static char umid[UMID_LEN] = { 0 };
+
+/* Changed by set_uml_dir and make_uml_dir, which are run early in boot */
+static char *uml_dir = UML_DIR;
+
+static int __init make_uml_dir(void)
+{
+ char dir[512] = { '\0' };
+ int len, err;
+
+ if (*uml_dir == '~') {
+ char *home = getenv("HOME");
+
+ err = -ENOENT;
+ if (home == NULL) {
+ printk(UM_KERN_ERR
+ "%s: no value in environment for $HOME\n",
+ __func__);
+ goto err;
+ }
+ strscpy(dir, home);
+ uml_dir++;
+ }
+ strlcat(dir, uml_dir, sizeof(dir));
+ len = strlen(dir);
+ if (len > 0 && dir[len - 1] != '/')
+ strlcat(dir, "/", sizeof(dir));
+
+ err = -ENOMEM;
+ uml_dir = malloc(strlen(dir) + 1);
+ if (uml_dir == NULL) {
+ printk(UM_KERN_ERR "%s : malloc failed, errno = %d\n",
+ __func__, errno);
+ goto err;
+ }
+ strcpy(uml_dir, dir);
+
+ if ((mkdir(uml_dir, 0777) < 0) && (errno != EEXIST)) {
+ printk(UM_KERN_ERR "Failed to mkdir '%s': %s\n",
+ uml_dir, strerror(errno));
+ err = -errno;
+ goto err_free;
+ }
+ return 0;
+
+err_free:
+ free(uml_dir);
+err:
+ uml_dir = NULL;
+ return err;
+}
+
+/*
+ * Unlinks the files contained in @dir and then removes @dir.
+ * Doesn't handle directory trees, so it's not like rm -rf, but almost such. We
+ * ignore ENOENT errors for anything (they happen, strangely enough - possibly
+ * due to races between multiple dying UML threads).
+ */
+static int remove_files_and_dir(char *dir)
+{
+ DIR *directory;
+ struct dirent *ent;
+ int len;
+ char file[256];
+ int ret;
+
+ directory = opendir(dir);
+ if (directory == NULL) {
+ if (errno != ENOENT)
+ return -errno;
+ else
+ return 0;
+ }
+
+ while ((ent = readdir(directory)) != NULL) {
+ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+ continue;
+ len = strlen(dir) + strlen("/") + strlen(ent->d_name) + 1;
+ if (len > sizeof(file)) {
+ ret = -E2BIG;
+ goto out;
+ }
+
+ sprintf(file, "%s/%s", dir, ent->d_name);
+ if (unlink(file) < 0 && errno != ENOENT) {
+ ret = -errno;
+ goto out;
+ }
+ }
+
+ if (rmdir(dir) < 0 && errno != ENOENT) {
+ ret = -errno;
+ goto out;
+ }
+
+ ret = 0;
+out:
+ closedir(directory);
+ return ret;
+}
+
+/*
+ * This says that there isn't already a user of the specified directory even if
+ * there are errors during the checking. This is because if these errors
+ * happen, the directory is unusable by the pre-existing UML, so we might as
+ * well take it over. This could happen either by
+ * the existing UML somehow corrupting its umid directory
+ * something other than UML sticking stuff in the directory
+ * this boot racing with a shutdown of the other UML
+ * In any of these cases, the directory isn't useful for anything else.
+ *
+ * Boolean return: 1 if in use, 0 otherwise.
+ */
+static inline int is_umdir_used(char *dir)
+{
+ char pid[sizeof("nnnnnnnnn")], *end, *file;
+ int fd, p, n;
+ size_t filelen = strlen(dir) + sizeof("/pid") + 1;
+
+ file = malloc(filelen);
+ if (!file)
+ return -ENOMEM;
+
+ snprintf(file, filelen, "%s/pid", dir);
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ fd = -errno;
+ if (fd != -ENOENT) {
+ printk(UM_KERN_ERR "is_umdir_used : couldn't open pid "
+ "file '%s', err = %d\n", file, -fd);
+ }
+ goto out;
+ }
+
+ n = read(fd, pid, sizeof(pid));
+ if (n < 0) {
+ printk(UM_KERN_ERR "is_umdir_used : couldn't read pid file "
+ "'%s', err = %d\n", file, errno);
+ goto out_close;
+ } else if (n == 0) {
+ printk(UM_KERN_ERR "is_umdir_used : couldn't read pid file "
+ "'%s', 0-byte read\n", file);
+ goto out_close;
+ }
+
+ p = strtoul(pid, &end, 0);
+ if (end == pid) {
+ printk(UM_KERN_ERR "is_umdir_used : couldn't parse pid file "
+ "'%s', errno = %d\n", file, errno);
+ goto out_close;
+ }
+
+ if ((kill(p, 0) == 0) || (errno != ESRCH)) {
+ printk(UM_KERN_ERR "umid \"%s\" is already in use by pid %d\n",
+ umid, p);
+ return 1;
+ }
+
+out_close:
+ close(fd);
+out:
+ free(file);
+ return 0;
+}
+
+/*
+ * Try to remove the directory @dir unless it's in use.
+ * Precondition: @dir exists.
+ * Returns 0 for success, < 0 for failure in removal or if the directory is in
+ * use.
+ */
+static int umdir_take_if_dead(char *dir)
+{
+ int ret;
+ if (is_umdir_used(dir))
+ return -EEXIST;
+
+ ret = remove_files_and_dir(dir);
+ if (ret) {
+ printk(UM_KERN_ERR "is_umdir_used - remove_files_and_dir "
+ "failed with err = %d\n", ret);
+ }
+ return ret;
+}
+
+static void __init create_pid_file(void)
+{
+ char pid[sizeof("nnnnnnnnn")], *file;
+ int fd, n;
+
+ n = strlen(uml_dir) + UMID_LEN + sizeof("/pid");
+ file = malloc(n);
+ if (!file)
+ return;
+
+ if (umid_file_name("pid", file, n))
+ goto out;
+
+ fd = open(file, O_RDWR | O_CREAT | O_EXCL, 0644);
+ if (fd < 0) {
+ printk(UM_KERN_ERR "Open of machine pid file \"%s\" failed: "
+ "%s\n", file, strerror(errno));
+ goto out;
+ }
+
+ snprintf(pid, sizeof(pid), "%d\n", getpid());
+ n = write(fd, pid, strlen(pid));
+ if (n != strlen(pid))
+ printk(UM_KERN_ERR "Write of pid file failed - err = %d\n",
+ errno);
+
+ close(fd);
+out:
+ free(file);
+}
+
+int __init set_umid(char *name)
+{
+ if (strlen(name) > UMID_LEN - 1)
+ return -E2BIG;
+
+ strscpy(umid, name);
+
+ return 0;
+}
+
+/* Changed in make_umid, which is called during early boot */
+static int umid_setup = 0;
+
+static int __init make_umid(void)
+{
+ int fd, err;
+ char tmp[256];
+
+ if (umid_setup)
+ return 0;
+
+ make_uml_dir();
+
+ if (*umid == '\0') {
+ strscpy(tmp, uml_dir);
+ strlcat(tmp, "XXXXXX", sizeof(tmp));
+ fd = mkstemp(tmp);
+ if (fd < 0) {
+ printk(UM_KERN_ERR "make_umid - mkstemp(%s) failed: "
+ "%s\n", tmp, strerror(errno));
+ err = -errno;
+ goto err;
+ }
+
+ close(fd);
+
+ set_umid(&tmp[strlen(uml_dir)]);
+
+ /*
+ * There's a nice tiny little race between this unlink and
+ * the mkdir below. It'd be nice if there were a mkstemp
+ * for directories.
+ */
+ if (unlink(tmp)) {
+ err = -errno;
+ goto err;
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s%s", uml_dir, umid);
+ err = mkdir(tmp, 0777);
+ if (err < 0) {
+ err = -errno;
+ if (err != -EEXIST)
+ goto err;
+
+ if (umdir_take_if_dead(tmp) < 0)
+ goto err;
+
+ err = mkdir(tmp, 0777);
+ }
+ if (err) {
+ err = -errno;
+ printk(UM_KERN_ERR "Failed to create '%s' - err = %d\n", umid,
+ errno);
+ goto err;
+ }
+
+ umid_setup = 1;
+
+ create_pid_file();
+
+ err = 0;
+ err:
+ return err;
+}
+
+static int __init make_umid_init(void)
+{
+ if (!make_umid())
+ return 0;
+
+ /*
+ * If initializing with the given umid failed, then try again with
+ * a random one.
+ */
+ printk(UM_KERN_ERR "Failed to initialize umid \"%s\", trying with a "
+ "random umid\n", umid);
+ *umid = '\0';
+ make_umid();
+
+ return 0;
+}
+
+__initcall(make_umid_init);
+
+int __init umid_file_name(char *name, char *buf, int len)
+{
+ int n, err;
+
+ err = make_umid();
+ if (err)
+ return err;
+
+ n = snprintf(buf, len, "%s%s/%s", uml_dir, umid, name);
+ if (n >= len) {
+ printk(UM_KERN_ERR "umid_file_name : buffer too short\n");
+ return -E2BIG;
+ }
+
+ return 0;
+}
+
+char *get_umid(void)
+{
+ return umid;
+}
+
+static int __init set_uml_dir(char *name, int *add)
+{
+ *add = 0;
+
+ if (*name == '\0') {
+ os_warn("uml_dir can't be an empty string\n");
+ return 0;
+ }
+
+ if (name[strlen(name) - 1] == '/') {
+ uml_dir = name;
+ return 0;
+ }
+
+ uml_dir = malloc(strlen(name) + 2);
+ if (uml_dir == NULL) {
+ os_warn("Failed to malloc uml_dir - error = %d\n", errno);
+
+ /*
+ * Return 0 here because do_initcalls doesn't look at
+ * the return value.
+ */
+ return 0;
+ }
+ sprintf(uml_dir, "%s/", name);
+
+ return 0;
+}
+
+__uml_setup("uml_dir=", set_uml_dir,
+"uml_dir=<directory>\n"
+" The location to place the pid and umid files.\n\n"
+);
+
+static void remove_umid_dir(void)
+{
+ char *dir, err;
+
+ dir = malloc(strlen(uml_dir) + UMID_LEN + 1);
+ if (!dir)
+ return;
+
+ sprintf(dir, "%s%s", uml_dir, umid);
+ err = remove_files_and_dir(dir);
+ if (err)
+ os_warn("%s - remove_files_and_dir failed with err = %d\n",
+ __func__, err);
+
+ free(dir);
+}
+
+__uml_exitcall(remove_umid_dir);
diff --git a/arch/um/os-Linux/user_syms.c b/arch/um/os-Linux/user_syms.c
new file mode 100644
index 000000000..67f611231
--- /dev/null
+++ b/arch/um/os-Linux/user_syms.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+#define __NO_FORTIFY
+#include <linux/types.h>
+#include <linux/module.h>
+
+/*
+ * This file exports some critical string functions and compiler
+ * built-in functions (where calls are emitted by the compiler
+ * itself that we cannot avoid even in kernel code) to modules.
+ *
+ * "_user.c" code that previously used exports here such as hostfs
+ * really should be considered part of the 'hypervisor' and define
+ * its own API boundary like hostfs does now; don't add exports to
+ * this file for such cases.
+ */
+
+/* If it's not defined, the export is included in lib/string.c.*/
+#ifdef __HAVE_ARCH_STRSTR
+#undef strstr
+EXPORT_SYMBOL(strstr);
+#endif
+
+#ifndef __x86_64__
+#undef memcpy
+extern void *memcpy(void *, const void *, size_t);
+EXPORT_SYMBOL(memcpy);
+extern void *memmove(void *, const void *, size_t);
+EXPORT_SYMBOL(memmove);
+#undef memset
+extern void *memset(void *, int, size_t);
+EXPORT_SYMBOL(memset);
+#endif
+
+#ifdef _FORTIFY_SOURCE
+extern int __sprintf_chk(char *str, int flag, size_t len, const char *format);
+EXPORT_SYMBOL(__sprintf_chk);
+#endif
diff --git a/arch/um/os-Linux/util.c b/arch/um/os-Linux/util.c
new file mode 100644
index 000000000..e3ad71a0d
--- /dev/null
+++ b/arch/um/os-Linux/util.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <sys/random.h>
+#include <init.h>
+#include <os.h>
+
+void stack_protections(unsigned long address)
+{
+ if (mprotect((void *) address, UM_THREAD_SIZE, PROT_READ | PROT_WRITE) < 0)
+ panic("protecting stack failed, errno = %d", errno);
+}
+
+int raw(int fd)
+{
+ struct termios tt;
+ int err;
+
+ CATCH_EINTR(err = tcgetattr(fd, &tt));
+ if (err < 0)
+ return -errno;
+
+ cfmakeraw(&tt);
+
+ CATCH_EINTR(err = tcsetattr(fd, TCSADRAIN, &tt));
+ if (err < 0)
+ return -errno;
+
+ /*
+ * XXX tcsetattr could have applied only some changes
+ * (and cfmakeraw() is a set of changes)
+ */
+ return 0;
+}
+
+void setup_machinename(char *machine_out)
+{
+ struct utsname host;
+
+ uname(&host);
+#if IS_ENABLED(CONFIG_UML_X86)
+# if !IS_ENABLED(CONFIG_64BIT)
+ if (!strcmp(host.machine, "x86_64")) {
+ strcpy(machine_out, "i686");
+ return;
+ }
+# else
+ if (!strcmp(host.machine, "i686")) {
+ strcpy(machine_out, "x86_64");
+ return;
+ }
+# endif
+#endif
+ strcpy(machine_out, host.machine);
+}
+
+void setup_hostinfo(char *buf, int len)
+{
+ struct utsname host;
+
+ uname(&host);
+ snprintf(buf, len, "%s %s %s %s %s", host.sysname, host.nodename,
+ host.release, host.version, host.machine);
+}
+
+/*
+ * We cannot use glibc's abort(). It makes use of tgkill() which
+ * has no effect within UML's kernel threads.
+ * After that glibc would execute an invalid instruction to kill
+ * the calling process and UML crashes with SIGSEGV.
+ */
+static inline void __attribute__ ((noreturn)) uml_abort(void)
+{
+ sigset_t sig;
+
+ fflush(NULL);
+
+ if (!sigemptyset(&sig) && !sigaddset(&sig, SIGABRT))
+ sigprocmask(SIG_UNBLOCK, &sig, 0);
+
+ for (;;)
+ if (kill(getpid(), SIGABRT) < 0)
+ exit(127);
+}
+
+ssize_t os_getrandom(void *buf, size_t len, unsigned int flags)
+{
+ return getrandom(buf, len, flags);
+}
+
+/*
+ * UML helper threads must not handle SIGWINCH/INT/TERM
+ */
+void os_fix_helper_signals(void)
+{
+ signal(SIGWINCH, SIG_IGN);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+}
+
+void os_dump_core(void)
+{
+ int pid;
+
+ signal(SIGSEGV, SIG_DFL);
+
+ /*
+ * We are about to SIGTERM this entire process group to ensure that
+ * nothing is around to run after the kernel exits. The
+ * kernel wants to abort, not die through SIGTERM, so we
+ * ignore it here.
+ */
+
+ signal(SIGTERM, SIG_IGN);
+ kill(0, SIGTERM);
+ /*
+ * Most of the other processes associated with this UML are
+ * likely sTopped, so give them a SIGCONT so they see the
+ * SIGTERM.
+ */
+ kill(0, SIGCONT);
+
+ /*
+ * Now, having sent signals to everyone but us, make sure they
+ * die by ptrace. Processes can survive what's been done to
+ * them so far - the mechanism I understand is receiving a
+ * SIGSEGV and segfaulting immediately upon return. There is
+ * always a SIGSEGV pending, and (I'm guessing) signals are
+ * processed in numeric order so the SIGTERM (signal 15 vs
+ * SIGSEGV being signal 11) is never handled.
+ *
+ * Run a waitpid loop until we get some kind of error.
+ * Hopefully, it's ECHILD, but there's not a lot we can do if
+ * it's something else. Tell os_kill_ptraced_process not to
+ * wait for the child to report its death because there's
+ * nothing reasonable to do if that fails.
+ */
+
+ while ((pid = waitpid(-1, NULL, WNOHANG | __WALL)) > 0)
+ os_kill_ptraced_process(pid, 0);
+
+ uml_abort();
+}
+
+void um_early_printk(const char *s, unsigned int n)
+{
+ printf("%.*s", n, s);
+}
+
+static int quiet_info;
+
+static int __init quiet_cmd_param(char *str, int *add)
+{
+ quiet_info = 1;
+ return 0;
+}
+
+__uml_setup("quiet", quiet_cmd_param,
+"quiet\n"
+" Turns off information messages during boot.\n\n");
+
+/*
+ * The os_info/os_warn functions will be called by helper threads. These
+ * have a very limited stack size and using the libc formatting functions
+ * may overflow the stack.
+ * So pull in the kernel vscnprintf and use that instead with a fixed
+ * on-stack buffer.
+ */
+int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+
+void os_info(const char *fmt, ...)
+{
+ char buf[256];
+ va_list list;
+ int len;
+
+ if (quiet_info)
+ return;
+
+ va_start(list, fmt);
+ len = vscnprintf(buf, sizeof(buf), fmt, list);
+ fwrite(buf, len, 1, stderr);
+ va_end(list);
+}
+
+void os_warn(const char *fmt, ...)
+{
+ char buf[256];
+ va_list list;
+ int len;
+
+ va_start(list, fmt);
+ len = vscnprintf(buf, sizeof(buf), fmt, list);
+ fwrite(buf, len, 1, stderr);
+ va_end(list);
+}