Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+23
View File
@@ -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
+50
View File
@@ -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;
}
}
}
+150
View File
@@ -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
+726
View File
@@ -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;
}
+236
View File
@@ -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);
}
+36
View File
@@ -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 */
+145
View File
@@ -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);
}
+265
View File
@@ -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);
}
+246
View File
@@ -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);
}
+211
View File
@@ -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;
}
+43
View File
@@ -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);
}
+304
View File
@@ -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();
}
+506
View File
@@ -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
@@ -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
+284
View File
@@ -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;
}
+908
View File
@@ -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 */
}
+148
View File
@@ -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();
}
+495
View File
@@ -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);
}
+157
View File
@@ -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);
}
+60
View File
@@ -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;
}
+409
View File
@@ -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);
+37
View File
@@ -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
+209
View File
@@ -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);
}