diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-14 13:36:11 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-14 13:36:11 +0100 |
| commit | dc7bf241c0b573b997ae991af2425dcb61695db5 (patch) | |
| tree | d4934de97479ea9a2a11969c612e9be3420cdbab | |
| parent | 600ff6eaf5fa12f8010705ed3bf5de22cd6f216e (diff) | |
| parent | 4ad423aa2108485f8bc5dde51fdf9c1ed979ee4b (diff) | |
| download | linux-next-dc7bf241c0b573b997ae991af2425dcb61695db5.tar.gz linux-next-dc7bf241c0b573b997ae991af2425dcb61695db5.zip | |
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/uml/linux.git
# Conflicts:
# arch/um/Kconfig
56 files changed, 906 insertions, 516 deletions
diff --git a/Documentation/virt/index.rst b/Documentation/virt/index.rst index c1f0bbc37315..142134ed45c0 100644 --- a/Documentation/virt/index.rst +++ b/Documentation/virt/index.rst @@ -9,6 +9,7 @@ Virtualization Support kvm/index uml/user_mode_linux_howto_v2 + uml/nommu-uml paravirt_ops guest-halt-polling ne_overview diff --git a/Documentation/virt/uml/nommu-uml.rst b/Documentation/virt/uml/nommu-uml.rst new file mode 100644 index 000000000000..105e596ae1cc --- /dev/null +++ b/Documentation/virt/uml/nommu-uml.rst @@ -0,0 +1,54 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. contents:: :local: + +Execution model +=============== + +When emulating a system with an MMU, UML gives every ``mm_struct`` its own +host process with its own host address space, and keeps that address space +in sync with the kernel's page tables by issuing ``mmap()``/``munmap()`` +into it. + +NOMMU has no per-process address spaces: there is a single "physical" address +space that the kernel and every userspace process share. UML models this with +a **userspace runner** - an ordinary uml-userspace stub host process, started +exactly like an MMU userspace process, but with the whole of "physical" memory +(the physmem file) mapped into it at the kernel's addresses: + +- The runner ``exec()``'s the stub just like the MMU case. The only extra step + is that, once it is up, the kernel maps the entire physmem file into it in one + go, at the same addresses the kernel uses. +- Because NOMMU never remaps, that single mapping is all a runner ever needs: + there is no per-mm host process, no TLB sync and no ``mmap()`` of individual + page-table entries into a child. Userspace code and data - placed in memory + by the loader - are directly executable and accessible. +- The runner processes hold no per-task state: the kernel loads a task's state + on every entry, so there's just one process per CPU. + +Running a task's userspace therefore only means pointing a runner at that +task's code and letting it run until the next syscall or signal + +Building and running +==================== + +Configure and build the ARCH=um kernel normally, but deselect CONFIG_MMU. + +KUnit tests can be run in NOMMU mode with:: + + ./tools/testing/kunit/kunit.py run \ + --kconfig_add CONFIG_MMU=n --kconfig_add CONFIG_KUNIT_UML_PCI=n + +Running a normal userspace requires NOMMU-aware binaries. There is no stock +x86_64 NOMMU distribution, but a prebuilt Alpine image with musl-libc and +busybox built for NOMMU is available and can be turned into a root image:: + + cid=$(docker create ghcr.io/thehajime/alpine:3.20.3-um-nommu) + docker export "$cid" > alpine.tar + docker rm "$cid" + mkdir alpine-root && tar xf alpine.tar -C alpine-root + mke2fs -q -F -t ext4 -d alpine-root alpine.ext4 1200M + +Then boot it:: + + ./linux ubd0=./alpine.ext4 root=/dev/ubda rw mem=1024m init=/sbin/init diff --git a/arch/um/Kconfig b/arch/um/Kconfig index 94b8ff70f578..7245e3ada1c5 100644 --- a/arch/um/Kconfig +++ b/arch/um/Kconfig @@ -17,9 +17,9 @@ config UML select ARCH_HAS_KCOV select ARCH_HAS_STRNCPY_FROM_USER select ARCH_HAS_STRNLEN_USER - select ARCH_HAS_STRICT_KERNEL_RWX + select ARCH_HAS_STRICT_KERNEL_RWX if MMU select HAVE_ARCH_AUDITSYSCALL - select HAVE_ARCH_KASAN if X86_64 + select HAVE_ARCH_KASAN if X86_64 && MMU select HAVE_ARCH_KASAN_VMALLOC if HAVE_ARCH_KASAN select HAVE_ARCH_SECCOMP_FILTER select HAVE_ASM_MODVERSIONS @@ -27,6 +27,7 @@ config UML select HAVE_DEBUG_KMEMLEAK select HAVE_DEBUG_BUGVERBOSE select HAVE_PAGE_SIZE_4KB + select HAVE_PERF_EVENTS select NO_DMA if !UML_DMA_EMULATION select OF_EARLY_FLATTREE if OF select GENERIC_IRQ_SHOW @@ -37,17 +38,22 @@ config UML select ARCH_SUPPORTS_LTO_CLANG_THIN select TRACE_IRQFLAGS_SUPPORT select TTY # Needed for line.c - select HAVE_ARCH_VMAP_STACK + select HAVE_ARCH_VMAP_STACK if MMU select HAVE_RUST select ARCH_HAS_UBSAN select HAVE_ARCH_TRACEHOOK select HAVE_SYSCALL_TRACEPOINTS select THREAD_INFO_IN_TASK select SPARSE_IRQ + select UACCESS_MEMCPY if !MMU + select GENERIC_STRNLEN_USER if !MMU + select GENERIC_STRNCPY_FROM_USER if !MMU config MMU - bool + bool "MMU support" default y + help + Turning this off allows a nommu build, say Y. config UML_DMA_EMULATION bool @@ -209,27 +215,15 @@ config MCONSOLE It is safe to say 'Y' here. -config MAGIC_SYSRQ - bool "Magic SysRq key" - depends on MCONSOLE - help - If you say Y here, you will have some control over the system even - if the system crashes for example during kernel debugging (e.g., you - will be able to flush the buffer cache to disk, reboot the system - immediately or dump some status information). A key for each of the - possible requests is provided. - - This is the feature normally accomplished by pressing a key - while holding SysRq (Alt+PrintScreen). - - On UML, this is accomplished by sending a "sysrq" command with - mconsole, followed by the letter for the requested command. - - The keys are documented in <file:Documentation/admin-guide/sysrq.rst>. Don't say Y - unless you really know what this hack does. +config ARCH_FORCE_MAX_ORDER + int "Order of maximal physically contiguous allocations" if EXPERT + default "10" if MMU + default "16" if !MMU config KERNEL_STACK_ORDER int "Kernel stack size order" + default 3 if !MMU + range 3 10 if !MMU default 2 if 64BIT range 2 10 if 64BIT default 1 if !64BIT diff --git a/arch/um/Makefile b/arch/um/Makefile index 937639edc295..d930c5f25fb9 100644 --- a/arch/um/Makefile +++ b/arch/um/Makefile @@ -134,9 +134,10 @@ LINK_WRAPS = -Wl,--wrap,malloc -Wl,--wrap,free -Wl,--wrap,calloc # Avoid binutils 2.39+ warnings by marking the stack non-executable and # ignorning warnings for the kallsyms sections. LDFLAGS_EXECSTACK = -z noexecstack -ifeq ($(CONFIG_LD_IS_BFD),y) -LDFLAGS_EXECSTACK += $(call ld-option,--no-warn-rwx-segments) -endif +# check --no-warn-rwx-segments with CC-as-linker since that gets used +# for vmlinux and is where we need the option, in particular with LLVM=1 +# it might be different links (ld.bfd vs. ld.lld) +LDFLAGS_EXECSTACK += $(call try-run,$(CC) -Wl$(comma)--no-warn-rwx-segments -shared -x c /dev/null -o "$$TMP",--no-warn-rwx-segments) LD_FLAGS_CMDLINE = $(foreach opt,$(KBUILD_LDFLAGS) $(LDFLAGS_EXECSTACK),-Wl,$(opt)) diff --git a/arch/um/drivers/Kconfig b/arch/um/drivers/Kconfig index 6a0354ca032f..655e5a270c97 100644 --- a/arch/um/drivers/Kconfig +++ b/arch/um/drivers/Kconfig @@ -159,6 +159,7 @@ config UML_RTC config UML_PCI bool + depends on MMU select FORCE_PCI select IRQ_MSI_LIB select UML_IOMEM_EMULATION @@ -169,6 +170,7 @@ config UML_PCI config UML_PCI_OVER_VIRTIO bool "Enable PCI over VIRTIO device simulation" # in theory, just VIRTIO is enough, but that causes recursion + depends on MMU depends on VIRTIO_UML select UML_PCI @@ -183,6 +185,7 @@ config UML_PCI_OVER_VIRTIO_DEVICE_ID config UML_PCI_OVER_VFIO bool "Enable VFIO-based PCI passthrough" + depends on MMU select UML_PCI help This driver provides support for VFIO-based PCI passthrough. diff --git a/arch/um/drivers/cow_user.c b/arch/um/drivers/cow_user.c index dc1d1bcd85ec..bb9f05ac70cf 100644 --- a/arch/um/drivers/cow_user.c +++ b/arch/um/drivers/cow_user.c @@ -279,7 +279,7 @@ int file_reader(__u64 offset, char *buf, int len, void *arg) return pread(fd, buf, len, offset); } -/* XXX Need to sanity-check the values read from the header */ +/* Sanity checks are performed after reading each header version below. */ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, __u32 *version_out, char **backing_file_out, @@ -325,6 +325,12 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, *sectorsize_out = header->v1.sectorsize; *bitmap_offset_out = sizeof(header->v1); *align_out = *sectorsize_out; + if (!memchr(header->v1.backing_file, '\0', + sizeof(header->v1.backing_file))) { + cow_printf("%s - V1 backing_file not terminated\n", + __func__); + goto out; + } file = header->v1.backing_file; } else if (version == 2) { @@ -338,6 +344,12 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, *sectorsize_out = be32toh(header->v2.sectorsize); *bitmap_offset_out = sizeof(header->v2); *align_out = *sectorsize_out; + if (!memchr(header->v2.backing_file, '\0', + sizeof(header->v2.backing_file))) { + cow_printf("%s - V2 backing_file not terminated\n", + __func__); + goto out; + } file = header->v2.backing_file; } /* This is very subtle - see above at union cow_header definition */ @@ -354,8 +366,15 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, if (*align_out == 0) { cow_printf("read_cow_header - invalid COW header, " "align == 0\n"); + goto out; } *bitmap_offset_out = ROUND_UP(sizeof(header->v3), *align_out); + if (!memchr(header->v3.backing_file, '\0', + sizeof(header->v3.backing_file))) { + cow_printf("%s - V3 backing_file not terminated\n", + __func__); + goto out; + } file = header->v3.backing_file; } else if (version == 3) { @@ -385,14 +404,27 @@ int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, if (*align_out == 0) { cow_printf("read_cow_header - invalid COW header, " "align == 0\n"); + goto out; } *bitmap_offset_out = ROUND_UP(sizeof(header->v3_b), *align_out); + if (!memchr(header->v3_b.backing_file, '\0', + sizeof(header->v3_b.backing_file))) { + cow_printf("%s - V3 backing_file not terminated\n", + __func__); + goto out; + } file = header->v3_b.backing_file; } else { cow_printf("read_cow_header - invalid COW version\n"); goto out; } + + if (*sectorsize_out <= 0) { + cow_printf("%s - invalid sectorsize %d\n", __func__, + *sectorsize_out); + goto out; + } err = -ENOMEM; *backing_file_out = cow_strdup(file); if (*backing_file_out == NULL) { diff --git a/arch/um/drivers/mconsole_kern.c b/arch/um/drivers/mconsole_kern.c index e2a9e8879f58..f01b7d8a07bc 100644 --- a/arch/um/drivers/mconsole_kern.c +++ b/arch/um/drivers/mconsole_kern.c @@ -106,9 +106,9 @@ void mconsole_version(struct mc_request *req) { char version[256]; - sprintf(version, "%s %s %s %s %s", utsname()->sysname, - utsname()->nodename, utsname()->release, utsname()->version, - utsname()->machine); + snprintf(version, sizeof(version), "%s %s %s %s %s", + utsname()->sysname, utsname()->nodename, utsname()->release, + utsname()->version, utsname()->machine); mconsole_reply(req, version, 0, 0); } @@ -117,10 +117,12 @@ void mconsole_log(struct mc_request *req) int len; char *ptr = req->request.data; - ptr += strlen("log "); + ptr += strlen(req->cmd->command); + ptr = skip_spaces(ptr); - len = req->len - (ptr - req->request.data); - printk(KERN_WARNING "%.*s", len, ptr); + len = req->request.len - (ptr - req->request.data); + if (len > 0) + printk(KERN_WARNING "%.*s", len, ptr); mconsole_reply(req, "", 0, 0); } diff --git a/arch/um/drivers/vector_kern.c b/arch/um/drivers/vector_kern.c index 8a70b3a62553..e902e78c5ff6 100644 --- a/arch/um/drivers/vector_kern.c +++ b/arch/um/drivers/vector_kern.c @@ -111,19 +111,26 @@ static void vector_reset_stats(struct vector_private *vp) * in vector_poll. */ - spin_lock(&vp->rx_queue->head_lock); + if (vp->rx_queue) + spin_lock(&vp->rx_queue->head_lock); + vp->estats.rx_queue_max = 0; vp->estats.rx_queue_running_average = 0; vp->estats.rx_encaps_errors = 0; vp->estats.sg_ok = 0; vp->estats.sg_linearized = 0; - spin_unlock(&vp->rx_queue->head_lock); + + if (vp->rx_queue) + spin_unlock(&vp->rx_queue->head_lock); + /* TX stats are modified with TX head_lock held * in vector_send. */ - spin_lock(&vp->tx_queue->head_lock); + if (vp->tx_queue) + spin_lock(&vp->tx_queue->head_lock); + vp->estats.tx_timeout_count = 0; vp->estats.tx_restart_queue = 0; vp->estats.tx_kicks = 0; @@ -131,7 +138,10 @@ static void vector_reset_stats(struct vector_private *vp) vp->estats.tx_flow_control_xoff = 0; vp->estats.tx_queue_max = 0; vp->estats.tx_queue_running_average = 0; - spin_unlock(&vp->tx_queue->head_lock); + + if (vp->tx_queue) + spin_unlock(&vp->tx_queue->head_lock); + } static int get_mtu(struct arglist *def) @@ -471,8 +481,6 @@ static void destroy_queue(struct vector_queue *qi) struct vector_private *vp = netdev_priv(qi->dev); struct mmsghdr *mmsg_vector; - if (qi == NULL) - return; /* deallocate any skbuffs - we rely on any unused to be * set to NULL. */ @@ -1166,15 +1174,15 @@ static int vector_poll(struct napi_struct *napi, int budget) if ((vp->options & VECTOR_TX) != 0) tx_enqueued = (vector_send(vp->tx_queue) > 0); - spin_lock(&vp->rx_queue->head_lock); - if ((vp->options & VECTOR_RX) > 0) + if ((vp->options & VECTOR_RX) > 0) { + spin_lock(&vp->rx_queue->head_lock); err = vector_mmsg_rx(vp, budget); - else { + spin_unlock(&vp->rx_queue->head_lock); + } else { err = vector_legacy_rx(vp); if (err > 0) err = 1; } - spin_unlock(&vp->rx_queue->head_lock); if (err > 0) work_done += err; @@ -1560,13 +1568,16 @@ static void vector_setup_etheraddr(struct net_device *dev, char *str) "Attempt to assign an invalid ethernet address to a device disallowed\n"); goto random; } + + eth_hw_addr_set(dev, addr); + if (!is_local_ether_addr(addr)) { + addr[0] |= 0x02; netdev_warn(dev, "Warning: Assigning a globally valid ethernet address to a device\n"); netdev_warn(dev, "You should set the 2nd rightmost bit in the first byte of the MAC,\n"); - netdev_warn(dev, "i.e. %02x:%02x:%02x:%02x:%02x:%02x\n", - addr[0] | 0x02, addr[1], addr[2], addr[3], addr[4], addr[5]); + netdev_warn(dev, "i.e. %pM\n", addr); } - eth_hw_addr_set(dev, addr); + return; random: diff --git a/arch/um/drivers/vector_user.c b/arch/um/drivers/vector_user.c index 2ea67e6fd067..3bee634f2102 100644 --- a/arch/um/drivers/vector_user.c +++ b/arch/um/drivers/vector_user.c @@ -93,6 +93,9 @@ struct arglist *uml_parse_vector_ifspec(char *arg) len = strlen(arg); for (pos = 0; pos < len; pos++) { if (next_starts) { + if (result->numargs >= MAXVARGS) + goto cleanup; + if (parsing_token) { result->tokens[result->numargs] = arg + pos; } else { diff --git a/arch/um/drivers/virtio_pcidev.c b/arch/um/drivers/virtio_pcidev.c index 2bf5a645db45..53cffd373890 100644 --- a/arch/um/drivers/virtio_pcidev.c +++ b/arch/um/drivers/virtio_pcidev.c @@ -490,13 +490,24 @@ static int virtio_pcidev_init_vqs(struct virtio_pcidev_device *dev) return 0; } +static void virtio_pcidev_del_vqs(struct virtio_device *vdev, + struct virtio_pcidev_device *dev) +{ + void *msg; + + while ((msg = virtqueue_detach_unused_buf(dev->irq_vq))) + kfree(msg); + + vdev->config->del_vqs(vdev); +} + static void __virtio_pcidev_virtio_platform_remove(struct virtio_device *vdev, struct virtio_pcidev_device *dev) { um_pci_platform_device_unregister(&dev->pdev); virtio_reset_device(vdev); - vdev->config->del_vqs(vdev); + virtio_pcidev_del_vqs(vdev, dev); kfree(dev); } @@ -526,7 +537,7 @@ err_unregister: um_pci_platform_device_unregister(&dev->pdev); err_reset: virtio_reset_device(vdev); - vdev->config->del_vqs(vdev); + virtio_pcidev_del_vqs(vdev, dev); err_free: kfree(dev); return err; @@ -569,7 +580,7 @@ static int virtio_pcidev_virtio_probe(struct virtio_device *vdev) err_reset: virtio_reset_device(vdev); - vdev->config->del_vqs(vdev); + virtio_pcidev_del_vqs(vdev, dev); err_free: kfree(dev); return err; @@ -591,9 +602,9 @@ static void virtio_pcidev_virtio_remove(struct virtio_device *vdev) /* Stop all virtqueues */ virtio_reset_device(vdev); + virtio_pcidev_del_vqs(vdev, dev); dev->cmd_vq = NULL; dev->irq_vq = NULL; - vdev->config->del_vqs(vdev); kfree(dev); } diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c index 7425a8548141..30899ddbeae2 100644 --- a/arch/um/drivers/virtio_uml.c +++ b/arch/um/drivers/virtio_uml.c @@ -568,7 +568,7 @@ static void vhost_user_get_config(struct virtio_uml_device *vu_dev, goto free; } - rc = vhost_user_recv_resp(vu_dev, msg, msg_size); + rc = vhost_user_recv_resp(vu_dev, msg, payload_size); if (rc) { vu_err(vu_dev, "receiving VHOST_USER_GET_CONFIG response failed: %d\n", @@ -1274,14 +1274,7 @@ static void virtio_uml_remove(struct platform_device *pdev) /* Command line device list */ -static void vu_cmdline_release_dev(struct device *d) -{ -} - -static struct device vu_cmdline_parent = { - .init_name = "virtio-uml-cmdline", - .release = vu_cmdline_release_dev, -}; +static struct device *vu_cmdline_parent; static DEFINE_MUTEX(vu_cmdline_lock); static bool vu_cmdline_parent_registered; @@ -1333,11 +1326,10 @@ static int vu_cmdline_set_device(const char *device) return -EINVAL; if (!vu_cmdline_parent_registered) { - err = device_register(&vu_cmdline_parent); - if (err) { + vu_cmdline_parent = __root_device_register("virtio-uml-cmdline", NULL); + if (IS_ERR(vu_cmdline_parent)) { pr_err("Failed to register parent device!\n"); - put_device(&vu_cmdline_parent); - return err; + return PTR_ERR(vu_cmdline_parent); } vu_cmdline_parent_registered = true; } @@ -1352,7 +1344,7 @@ static int vu_cmdline_set_device(const char *device) pr_info("Registering device virtio-uml.%d id=%d at %s\n", vu_cmdline_id, virtio_device_id, socket_path); - pdev = platform_device_register_data(&vu_cmdline_parent, "virtio-uml", + pdev = platform_device_register_data(vu_cmdline_parent, "virtio-uml", vu_cmdline_id++, &pdata, sizeof(pdata)); err = PTR_ERR_OR_ZERO(pdev); @@ -1393,7 +1385,7 @@ static int vu_cmdline_get(char *buffer, const struct kernel_param *kp) buffer[0] = '\0'; if (vu_cmdline_parent_registered) - device_for_each_child(&vu_cmdline_parent, buffer, + device_for_each_child(vu_cmdline_parent, buffer, vu_cmdline_get_device); return strlen(buffer) + 1; } @@ -1417,9 +1409,9 @@ static void vu_unregister_cmdline_devices(void) guard(mutex)(&vu_cmdline_lock); if (vu_cmdline_parent_registered) { - device_for_each_child(&vu_cmdline_parent, NULL, + device_for_each_child(vu_cmdline_parent, NULL, vu_unregister_cmdline_device); - device_unregister(&vu_cmdline_parent); + root_device_unregister(vu_cmdline_parent); vu_cmdline_parent_registered = false; } } diff --git a/arch/um/include/asm/common.lds.S b/arch/um/include/asm/common.lds.S index fd481ac371de..6585cb93e6eb 100644 --- a/arch/um/include/asm/common.lds.S +++ b/arch/um/include/asm/common.lds.S @@ -11,6 +11,8 @@ RO_DATA(4096) + .lrodata : { *(.lrodata .lrodata.[0-9a-zA-Z_]* .lrodata..L*) } + .unprotected : { *(.unprotected) } . = ALIGN(4096); PROVIDE (_unprotected_end = .); diff --git a/arch/um/include/asm/futex.h b/arch/um/include/asm/futex.h index 780aa6bfc050..07ab81f09281 100644 --- a/arch/um/include/asm/futex.h +++ b/arch/um/include/asm/futex.h @@ -7,8 +7,61 @@ #include <asm/errno.h> +#ifdef CONFIG_MMU int arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr); int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, u32 oldval, u32 newval); +#else +#include <linux/atomic.h> +#include <asm-generic/futex.h> + +#ifdef CONFIG_SMP +static inline int +arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr) +{ + u32 oldval, newval; + u32 *p = (u32 __force *)uaddr; + + do { + oldval = READ_ONCE(*p); + + switch (op) { + case FUTEX_OP_SET: + newval = oparg; + break; + case FUTEX_OP_ADD: + newval = oldval + oparg; + break; + case FUTEX_OP_OR: + newval = oldval | oparg; + break; + case FUTEX_OP_ANDN: + newval = oldval & ~oparg; + break; + case FUTEX_OP_XOR: + newval = oldval ^ oparg; + break; + default: + return -ENOSYS; + } + } while (!try_cmpxchg(p, &oldval, newval)); + + *oval = oldval; + return 0; +} + +static inline int +futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, + u32 oldval, u32 newval) +{ + u32 *p = (u32 __force *)uaddr; + + *uval = cmpxchg(p, oldval, newval); + + return 0; +} +#endif /* CONFIG_SMP */ + +#endif /* CONFIG_MMU */ #endif diff --git a/arch/um/include/asm/mmu.h b/arch/um/include/asm/mmu.h index 07d48738b402..c0b9ce3215c4 100644 --- a/arch/um/include/asm/mmu.h +++ b/arch/um/include/asm/mmu.h @@ -21,6 +21,14 @@ typedef struct mm_context { spinlock_t sync_tlb_lock; unsigned long sync_tlb_range_from; unsigned long sync_tlb_range_to; + +#ifndef CONFIG_MMU + unsigned long end_brk; +#ifdef CONFIG_BINFMT_ELF_FDPIC + unsigned long exec_fdpic_loadmap; + unsigned long interp_fdpic_loadmap; +#endif +#endif /* !CONFIG_MMU */ } mm_context_t; #define INIT_MM_CONTEXT(mm) \ diff --git a/arch/um/include/asm/mmu_context.h b/arch/um/include/asm/mmu_context.h index c727e56ba116..528b217da285 100644 --- a/arch/um/include/asm/mmu_context.h +++ b/arch/um/include/asm/mmu_context.h @@ -18,11 +18,13 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, { } +#ifdef CONFIG_MMU #define init_new_context init_new_context extern int init_new_context(struct task_struct *task, struct mm_struct *mm); #define destroy_context destroy_context extern void destroy_context(struct mm_struct *mm); +#endif #include <asm-generic/mmu_context.h> diff --git a/arch/um/include/asm/perf_event.h b/arch/um/include/asm/perf_event.h new file mode 100644 index 000000000000..9f774e2db198 --- /dev/null +++ b/arch/um/include/asm/perf_event.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __UM_PERF_EVENT_H +#define __UM_PERF_EVENT_H + +/* nothing here - clearly no real hardware support */ + +#endif /* __UM_PERF_EVENT_H */ diff --git a/arch/um/include/asm/pgtable.h b/arch/um/include/asm/pgtable.h index 88ea8434364d..6304026a2b2e 100644 --- a/arch/um/include/asm/pgtable.h +++ b/arch/um/include/asm/pgtable.h @@ -13,6 +13,7 @@ #define _PAGE_PRESENT 0x001 #define _PAGE_NEEDSYNC 0x002 +#define _PAGE_EXEC 0x004 #define _PAGE_RW 0x020 #define _PAGE_USER 0x040 #define _PAGE_ACCESSED 0x080 @@ -55,14 +56,19 @@ extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; #define _PAGE_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED | _PAGE_DIRTY) #define _KERNPG_TABLE (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | _PAGE_DIRTY) #define _PAGE_CHG_MASK (PAGE_MASK | _PAGE_ACCESSED | _PAGE_DIRTY) -#define __PAGE_KERNEL_EXEC \ - (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED) -#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED) -#define PAGE_SHARED __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_USER | _PAGE_ACCESSED) -#define PAGE_COPY __pgprot(_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED) -#define PAGE_READONLY __pgprot(_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED) -#define PAGE_KERNEL __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED) -#define PAGE_KERNEL_EXEC __pgprot(__PAGE_KERNEL_EXEC) + +#define _PAGE_BASE (_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED) +#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_ACCESSED) +#define PAGE_SHARED __pgprot(_PAGE_BASE | _PAGE_RW) +#define PAGE_SHARED_EXEC __pgprot(_PAGE_BASE | _PAGE_RW | _PAGE_EXEC) +#define PAGE_COPY __pgprot(_PAGE_BASE) +#define PAGE_COPY_EXEC __pgprot(_PAGE_BASE | _PAGE_EXEC) +#define PAGE_READONLY __pgprot(_PAGE_BASE) +#define PAGE_READONLY_EXEC __pgprot(_PAGE_BASE | _PAGE_EXEC) + +#define _PAGE_KERNEL (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED) +#define PAGE_KERNEL __pgprot(_PAGE_KERNEL) +#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC) /* * The i386 can't do page protection for execute, and considers that the same @@ -117,7 +123,8 @@ static inline int pte_read(pte_t pte) static inline int pte_exec(pte_t pte) { - return !pte_get_bits(pte, _PAGE_PROTNONE); + return pte_get_bits(pte, _PAGE_EXEC) && + !pte_get_bits(pte, _PAGE_PROTNONE); } static inline int pte_write(pte_t pte) diff --git a/arch/um/include/asm/ptrace-generic.h b/arch/um/include/asm/ptrace-generic.h index 86d74f9d33cf..62e9916078ec 100644 --- a/arch/um/include/asm/ptrace-generic.h +++ b/arch/um/include/asm/ptrace-generic.h @@ -29,6 +29,12 @@ struct pt_regs { #define PTRACE_OLDSETOPTIONS 21 +#ifdef CONFIG_BINFMT_ELF_FDPIC +#define PTRACE_GETFDPIC 31 +#define PTRACE_GETFDPIC_EXEC 0 +#define PTRACE_GETFDPIC_INTERP 1 +#endif + struct task_struct; extern long subarch_ptrace(struct task_struct *child, long request, diff --git a/arch/um/include/asm/tlbflush.h b/arch/um/include/asm/tlbflush.h index 13a3009942be..f73b568a0859 100644 --- a/arch/um/include/asm/tlbflush.h +++ b/arch/um/include/asm/tlbflush.h @@ -30,6 +30,8 @@ * - flush_tlb_kernel_range(start, end) flushes a range of kernel pages */ +#ifdef CONFIG_MMU + extern int um_tlb_sync(struct mm_struct *mm); extern void flush_tlb_all(void); @@ -56,4 +58,22 @@ static inline void flush_tlb_kernel_range(unsigned long start, um_tlb_sync(&init_mm); } +#else /* !CONFIG_MMU */ + +/* + * With NOMMU the kernel and userspace share a single host address space, + * so there is nothing to synchronise and all TLB flushes are no-ops. + */ +static inline int um_tlb_sync(struct mm_struct *mm) { return 0; } +static inline void flush_tlb_all(void) { } +static inline void flush_tlb_mm(struct mm_struct *mm) { } +static inline void flush_tlb_page(struct vm_area_struct *vma, + unsigned long address) { } +static inline void flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end) { } +static inline void flush_tlb_kernel_range(unsigned long start, + unsigned long end) { } + +#endif /* CONFIG_MMU */ + #endif diff --git a/arch/um/include/asm/uaccess.h b/arch/um/include/asm/uaccess.h index 4417c8b1d37a..e493596f6663 100644 --- a/arch/um/include/asm/uaccess.h +++ b/arch/um/include/asm/uaccess.h @@ -18,6 +18,7 @@ #define __addr_range_nowrap(addr, size) \ ((unsigned long) (addr) <= ((unsigned long) (addr) + (size))) +#ifdef CONFIG_MMU extern unsigned long raw_copy_from_user(void *to, const void __user *from, unsigned long n); extern unsigned long raw_copy_to_user(void __user *to, const void *from, unsigned long n); extern unsigned long __clear_user(void __user *mem, unsigned long len); @@ -29,8 +30,6 @@ static inline int __access_ok(const void __user *ptr, unsigned long size); #define INLINE_COPY_USER -#include <asm-generic/uaccess.h> - static inline int __access_ok(const void __user *ptr, unsigned long size) { unsigned long addr = (unsigned long)ptr; @@ -62,5 +61,8 @@ do { \ barrier(); \ current->thread.segv_continue = NULL; \ } while (0) +#endif + +#include <asm-generic/uaccess.h> #endif diff --git a/arch/um/include/shared/as-layout.h b/arch/um/include/shared/as-layout.h index 02ef258e3395..7c4ccb6d9d37 100644 --- a/arch/um/include/shared/as-layout.h +++ b/arch/um/include/shared/as-layout.h @@ -29,6 +29,7 @@ #ifndef __ASSEMBLER__ +#include <linux/compiler_types.h> #include <sysdep/ptrace.h> struct task_struct; @@ -46,7 +47,7 @@ extern unsigned long brk_start; extern unsigned long stub_start; -extern int linux_main(int argc, char **argv, char **envp); +extern void __noreturn linux_main(int argc, char **argv, char **envp); extern void uml_finishsetup(void); struct siginfo; diff --git a/arch/um/include/shared/kern_util.h b/arch/um/include/shared/kern_util.h index 9812efd14ec0..7950d58a4ae8 100644 --- a/arch/um/include/shared/kern_util.h +++ b/arch/um/include/shared/kern_util.h @@ -6,6 +6,7 @@ #ifndef __KERN_UTIL_H__ #define __KERN_UTIL_H__ +#include <linux/compiler_types.h> #include <sysdep/ptrace.h> #include <sysdep/faultinfo.h> @@ -37,7 +38,7 @@ extern void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs extern void uml_pm_wake(void); -extern int start_uml(void); +extern void __noreturn start_uml(void); extern void uml_cleanup(void); extern void do_uml_exitcalls(void); diff --git a/arch/um/include/shared/longjmp.h b/arch/um/include/shared/longjmp.h index c53e43d980c8..b35ed756e3e4 100644 --- a/arch/um/include/shared/longjmp.h +++ b/arch/um/include/shared/longjmp.h @@ -2,11 +2,12 @@ #ifndef __UML_LONGJMP_H #define __UML_LONGJMP_H +#include <linux/compiler_types.h> #include <sysdep/archsetjmp.h> #include <os.h> extern int setjmp(jmp_buf); -extern void longjmp(jmp_buf, int); +extern void __noreturn longjmp(jmp_buf, int); #define UML_LONGJMP(buf, val) do { \ longjmp(*buf, val); \ diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h index b26e94292fc1..0067c5208c4c 100644 --- a/arch/um/include/shared/os.h +++ b/arch/um/include/shared/os.h @@ -15,6 +15,7 @@ #ifndef __UM_HOST__ #include <linux/types.h> #else +#include <stdbool.h> #include <sys/types.h> #endif @@ -196,6 +197,9 @@ extern int create_mem_file(unsigned long long len); /* tlb.c */ extern void report_enomem(void); +/* main.c */ +extern void __noreturn os_exit(bool reboot); + /* process.c */ pid_t os_reap_child(void); extern void os_alarm_process(int pid); @@ -295,7 +299,7 @@ extern int start_userspace(struct mm_id *mm_id); extern void userspace(struct uml_pt_regs *regs); extern void new_thread(void *stack, jmp_buf *buf, void (*handler)(void)); extern void switch_threads(jmp_buf *me, jmp_buf *you); -extern int start_idle_thread(void *stack, jmp_buf *switch_buf); +extern void __noreturn start_idle_thread(void *stack, jmp_buf *switch_buf); extern void initial_thread_cb_skas(void (*proc)(void *), void *arg); extern void halt_skas(void); diff --git a/arch/um/include/shared/skas/stub-data.h b/arch/um/include/shared/skas/stub-data.h index 27db38e95df9..1230a325debc 100644 --- a/arch/um/include/shared/skas/stub-data.h +++ b/arch/um/include/shared/skas/stub-data.h @@ -9,11 +9,20 @@ #define __STUB_DATA_H #include <linux/compiler_types.h> +#include <asm/unistd.h> #include <as-layout.h> #include <sysdep/tls.h> #include <sysdep/stub-data.h> #include <mm_id.h> +#ifndef __NR_close_range +# if defined(__alpha__) +# define __NR_close_range 546 +# else +# define __NR_close_range 436 +# endif +#endif + #define FUTEX_IN_CHILD 0 #define FUTEX_IN_KERN 1 diff --git a/arch/um/kernel/Makefile b/arch/um/kernel/Makefile index be60bc451b3f..b087b4278173 100644 --- a/arch/um/kernel/Makefile +++ b/arch/um/kernel/Makefile @@ -16,9 +16,13 @@ always-$(KBUILD_BUILTIN) := vmlinux.lds obj-y = config.o exec.o exitcode.o irq.o ksyms.o mem.o \ physmem.o process.o ptrace.o reboot.o sigio.o \ - signal.o sysrq.o time.o tlb.o trap.o \ + signal.o sysrq.o time.o trap.o \ um_arch.o umid.o kmsg_dump.o capflags.o skas/ obj-y += load_file.o +obj-$(CONFIG_MMU) += mem-pgtable.o tlb.o trap-mmu.o +ifneq ($(CONFIG_MMU),y) +obj-y += trap-nommu.o +endif obj-$(CONFIG_BLK_DEV_INITRD) += initrd.o obj-$(CONFIG_GPROF) += gprof_syms.o diff --git a/arch/um/kernel/dyn.lds.S b/arch/um/kernel/dyn.lds.S index ad3cefeff2ac..31adf9451507 100644 --- a/arch/um/kernel/dyn.lds.S +++ b/arch/um/kernel/dyn.lds.S @@ -20,9 +20,12 @@ SECTIONS __binary_start = .; . = ALIGN(4096); /* Init code and data */ _text = .; + __init_begin = .; INIT_TEXT_SECTION(PAGE_SIZE) + init.data : { INIT_DATA } . = ALIGN(PAGE_SIZE); + __init_end = .; /* Read-only sections, merged into text segment: */ .hash : { *(.hash) } @@ -98,10 +101,6 @@ SECTIONS #include <asm/common.lds.S> - __init_begin = .; - init.data : { INIT_DATA } - __init_end = .; - /* Ensure the __preinit_array_start label is properly aligned. We could instead move the label definition inside the section, but the linker would then create the section even if it turns out to @@ -117,6 +116,7 @@ SECTIONS .data : { INIT_TASK_DATA(KERNEL_STACK_SIZE) DATA_DATA + *(.ldata .ldata.[0-9a-zA-Z_]* .ldata..L*) *(.data.* .gnu.linkonce.d.*) SORT(CONSTRUCTORS) } @@ -159,6 +159,7 @@ SECTIONS __bss_start = .; *(.dynbss) *(.bss .bss.* .gnu.linkonce.b.*) + *(.lbss .lbss.[0-9a-zA-Z_]* .lbss..L*) *(COMMON) /* Align here to ensure that the .bss section occupies space up to _end. Align after .bss to ensure correct alignment even if the diff --git a/arch/um/kernel/mem-pgtable.c b/arch/um/kernel/mem-pgtable.c new file mode 100644 index 000000000000..e33894431acd --- /dev/null +++ b/arch/um/kernel/mem-pgtable.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + */ + +#include <linux/stddef.h> +#include <linux/module.h> +#include <linux/memblock.h> +#include <linux/swap.h> +#include <linux/slab.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <as-layout.h> +#include <init.h> +#include <kern.h> +#include <kern_util.h> +#include <mem_user.h> +#include <os.h> +#include <um_malloc.h> + + +/* Allocate and free page tables. */ + +pgd_t *pgd_alloc(struct mm_struct *mm) +{ + pgd_t *pgd = (pgd_t *)__get_free_page(GFP_KERNEL); + + if (pgd) { + memset(pgd, 0, USER_PTRS_PER_PGD * sizeof(pgd_t)); + memcpy(pgd + USER_PTRS_PER_PGD, + swapper_pg_dir + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + return pgd; +} + +static const pgprot_t protection_map[16] = { + [VM_NONE] = PAGE_NONE, + [VM_READ] = PAGE_READONLY, + [VM_WRITE] = PAGE_COPY, + [VM_WRITE | VM_READ] = PAGE_COPY, + [VM_EXEC] = PAGE_READONLY_EXEC, + [VM_EXEC | VM_READ] = PAGE_READONLY_EXEC, + [VM_EXEC | VM_WRITE] = PAGE_COPY_EXEC, + [VM_EXEC | VM_WRITE | VM_READ] = PAGE_COPY_EXEC, + [VM_SHARED] = PAGE_NONE, + [VM_SHARED | VM_READ] = PAGE_READONLY, + [VM_SHARED | VM_WRITE] = PAGE_SHARED, + [VM_SHARED | VM_WRITE | VM_READ] = PAGE_SHARED, + [VM_SHARED | VM_EXEC] = PAGE_READONLY_EXEC, + [VM_SHARED | VM_EXEC | VM_READ] = PAGE_READONLY_EXEC, + [VM_SHARED | VM_EXEC | VM_WRITE] = PAGE_SHARED_EXEC, + [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_SHARED_EXEC +}; +DECLARE_VM_GET_PAGE_PROT diff --git a/arch/um/kernel/mem.c b/arch/um/kernel/mem.c index 1eef0e42ef5d..2219827ac3ad 100644 --- a/arch/um/kernel/mem.c +++ b/arch/um/kernel/mem.c @@ -6,7 +6,6 @@ #include <linux/stddef.h> #include <linux/module.h> #include <linux/memblock.h> -#include <linux/mm.h> #include <linux/swap.h> #include <linux/slab.h> #include <linux/init.h> @@ -65,7 +64,8 @@ void __init arch_mm_preinit(void) * to be turned on. */ brk_end = PAGE_ALIGN((unsigned long) sbrk(0)); - map_memory(brk_end, __pa(brk_end), uml_reserved - brk_end, 1, 1, 0); + map_memory(brk_end, __pa(brk_end), uml_reserved - brk_end, 1, 1, + !IS_ENABLED(CONFIG_MMU)); memblock_free((void *)brk_end, uml_reserved - brk_end); uml_reserved = brk_end; min_low_pfn = PFN_UP(__pa(uml_reserved)); @@ -83,26 +83,17 @@ void __init arch_zone_limits_init(unsigned long *max_zone_pfns) } /* - * This can't do anything because nothing in the kernel image can be freed - * since it's not in kernel physical memory. + * We could munmap() this instead, but then libc allocations could + * land in this area and stray initdata access could erroneously + * succeed - just mprotect() it to reliably catch bad accesses. */ - void free_initmem(void) { -} + unsigned long start = PAGE_ALIGN((unsigned long)__init_begin); + unsigned long end = round_down((unsigned long)__init_end, PAGE_SIZE); -/* Allocate and free page tables. */ - -pgd_t *pgd_alloc(struct mm_struct *mm) -{ - pgd_t *pgd = __pgd_alloc(mm, 0); - - if (pgd) - memcpy(pgd + USER_PTRS_PER_PGD, - swapper_pg_dir + USER_PTRS_PER_PGD, - (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); - - return pgd; + if (end > start) + os_protect_memory((void *)start, end - start, 0, 0, 0); } void *uml_kmalloc(int size, int flags) @@ -110,26 +101,6 @@ void *uml_kmalloc(int size, int flags) return kmalloc(size, flags); } -static const pgprot_t protection_map[16] = { - [VM_NONE] = PAGE_NONE, - [VM_READ] = PAGE_READONLY, - [VM_WRITE] = PAGE_COPY, - [VM_WRITE | VM_READ] = PAGE_COPY, - [VM_EXEC] = PAGE_READONLY, - [VM_EXEC | VM_READ] = PAGE_READONLY, - [VM_EXEC | VM_WRITE] = PAGE_COPY, - [VM_EXEC | VM_WRITE | VM_READ] = PAGE_COPY, - [VM_SHARED] = PAGE_NONE, - [VM_SHARED | VM_READ] = PAGE_READONLY, - [VM_SHARED | VM_WRITE] = PAGE_SHARED, - [VM_SHARED | VM_WRITE | VM_READ] = PAGE_SHARED, - [VM_SHARED | VM_EXEC] = PAGE_READONLY, - [VM_SHARED | VM_EXEC | VM_READ] = PAGE_READONLY, - [VM_SHARED | VM_EXEC | VM_WRITE] = PAGE_SHARED, - [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_SHARED -}; -DECLARE_VM_GET_PAGE_PROT - void mark_rodata_ro(void) { unsigned long rodata_start = PFN_ALIGN(__start_rodata); diff --git a/arch/um/kernel/physmem.c b/arch/um/kernel/physmem.c index ae6ca373c261..ae9b8f3144b8 100644 --- a/arch/um/kernel/physmem.c +++ b/arch/um/kernel/physmem.c @@ -110,6 +110,13 @@ int phys_mapping(unsigned long phys, unsigned long long *offset_out) } EXPORT_SYMBOL(phys_mapping); +void report_enomem(void) +{ + printk(KERN_ERR "UML ran out of memory on the host side! " + "This can happen due to a memory limitation or " + "vm.max_map_count has been reached.\n"); +} + static int __init uml_mem_setup(char *line, int *add) { char *retptr; diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c index 63b38a3f73f7..dfca9e0aeec9 100644 --- a/arch/um/kernel/process.c +++ b/arch/um/kernel/process.c @@ -276,16 +276,12 @@ unsigned long arch_align_stack(unsigned long sp) } #endif -unsigned long __get_wchan(struct task_struct *p) +static unsigned long ___get_wchan(struct task_struct *p) { unsigned long stack_page, sp, ip; bool seen_sched = 0; stack_page = (unsigned long) task_stack_page(p); - /* Bail if the process has no kernel stack for some reason */ - if (stack_page == 0) - return 0; - sp = p->thread.switch_buf->JB_SP; /* * Bail if the stack pointer is below the bottom of the kernel @@ -306,4 +302,21 @@ unsigned long __get_wchan(struct task_struct *p) } return 0; + +} + +unsigned long __get_wchan(struct task_struct *p) +{ + unsigned long ret; + + /* Bail if the process has no kernel stack for some reason */ + if (!try_get_task_stack(p)) + return 0; + + ret = ___get_wchan(p); + + put_task_stack(p); + + return ret; + } diff --git a/arch/um/kernel/skas/Makefile b/arch/um/kernel/skas/Makefile index 3384be42691f..2cf82eba52f8 100644 --- a/arch/um/kernel/skas/Makefile +++ b/arch/um/kernel/skas/Makefile @@ -3,8 +3,12 @@ # Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) # -obj-y := stub.o mmu.o process.o syscall.o uaccess.o \ +obj-y := stub.o process.o syscall.o \ stub_exe_embed.o +obj-$(CONFIG_MMU) += mmu.o uaccess.o +ifneq ($(CONFIG_MMU),y) +obj-y += nommu.o +endif # Stub executable @@ -40,8 +44,14 @@ targets += stub_exe.dbg stub_exe $(stub_exe_objs-y) CFLAGS_stub.o := $(CFLAGS_NO_HARDENING) CFLAGS_stub_exe.o := $(CFLAGS_NO_HARDENING) -# Clang will call memset() from __builtin_alloca() when stack variable -# initialization is enabled, which is used in stub_exe.c. +# stub_signal_restorer() must not touch stack pointer, clang doesn't +# always omit the frame pointer automatically +CFLAGS_stub.o += -fomit-frame-pointer +CFLAGS_stub_exe.o += -fomit-frame-pointer + +# Clang's stack var initialisation can use memset() or similar and that +# cannot be done for this code since we embed/move the stub +CFLAGS_stub.o += $(call cc-option, -ftrivial-auto-var-init=uninitialized) CFLAGS_stub_exe.o += $(call cc-option, -ftrivial-auto-var-init=uninitialized) UNPROFILE_OBJS := stub.o stub_exe.o diff --git a/arch/um/kernel/skas/mmu.c b/arch/um/kernel/skas/mmu.c index b5017096028b..facb88d1e393 100644 --- a/arch/um/kernel/skas/mmu.c +++ b/arch/um/kernel/skas/mmu.c @@ -12,6 +12,7 @@ #include <asm/pgalloc.h> #include <asm/sections.h> #include <asm/mmu_context.h> +#include <asm/tlbflush.h> #include <as-layout.h> #include <os.h> #include <skas.h> @@ -40,6 +41,30 @@ void exit_turnstile(struct mm_id *mm_id) mutex_unlock(__get_turnstile(mm_id)); } +unsigned long current_stub_stack(void) +{ + if (current->mm == NULL) + return 0; + + return current->mm->context.id.stack; +} + +struct mm_id *current_mm_id(void) +{ + if (current->mm == NULL) + return NULL; + + return ¤t->mm->context.id; +} + +void current_mm_sync(void) +{ + if (current->mm == NULL) + return; + + um_tlb_sync(current->mm); +} + int init_new_context(struct task_struct *task, struct mm_struct *mm) { struct mm_id *new_id = &mm->context.id; diff --git a/arch/um/kernel/skas/nommu.c b/arch/um/kernel/skas/nommu.c new file mode 100644 index 000000000000..80d5c4eaa96b --- /dev/null +++ b/arch/um/kernel/skas/nommu.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/cpumask.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#include <linux/mutex.h> +#include <linux/panic.h> +#include <linux/smp.h> +#include <linux/threads.h> +#include <as-layout.h> +#include <mem.h> +#include <os.h> +#include <skas.h> +#include <mm_id.h> + +struct nommu_runner { + struct mm_id mm_id; + struct mutex turnstile; +}; + +static struct nommu_runner nommu_runners[NR_CPUS]; + +static struct nommu_runner *this_runner(void) +{ + return &nommu_runners[raw_smp_processor_id()]; +} + +struct mutex *__get_turnstile(struct mm_id *mm_id) +{ + return &container_of(mm_id, struct nommu_runner, mm_id)->turnstile; +} + +void enter_turnstile(struct mm_id *mm_id) +{ + mutex_lock(__get_turnstile(mm_id)); +} + +void exit_turnstile(struct mm_id *mm_id) +{ + mutex_unlock(__get_turnstile(mm_id)); +} + +unsigned long current_stub_stack(void) +{ + return this_runner()->mm_id.stack; +} + +struct mm_id *current_mm_id(void) +{ + return &this_runner()->mm_id; +} + +void current_mm_sync(void) +{ +} + +static int __init nommu_start_runners(void) +{ + unsigned long long offset; + struct nommu_runner *r; + int cpu, err, fd; + + fd = phys_mapping(uml_reserved - uml_physmem, &offset); + + for_each_possible_cpu(cpu) { + r = &nommu_runners[cpu]; + mutex_init(&r->turnstile); + + r->mm_id.stack = __get_free_pages(GFP_KERNEL | __GFP_ZERO, + ilog2(STUB_DATA_PAGES)); + if (!r->mm_id.stack) + panic("OOM allocating userspace stack for CPU %d", cpu); + + err = start_userspace(&r->mm_id); + if (err < 0) + panic("userspace startup for CPU %d failed: %d", + cpu, err); + + map(&r->mm_id, uml_reserved, high_physmem - uml_reserved, + UM_PROT_READ | UM_PROT_WRITE | UM_PROT_EXEC, fd, offset); + + err = syscall_stub_flush(&r->mm_id); + if (err < 0) + panic("physmem map/flush failed for CPU%d: %d", + cpu, err); + } + + return 0; +} +arch_initcall(nommu_start_runners); diff --git a/arch/um/kernel/skas/process.c b/arch/um/kernel/skas/process.c index 4a7673b0261a..f60541df209f 100644 --- a/arch/um/kernel/skas/process.c +++ b/arch/um/kernel/skas/process.c @@ -9,8 +9,6 @@ #include <linux/sched/task.h> #include <linux/smp-internal.h> -#include <asm/tlbflush.h> - #include <as-layout.h> #include <kern.h> #include <os.h> @@ -29,7 +27,7 @@ static int __init start_kernel_proc(void *unused) char cpu_irqstacks[NR_CPUS][THREAD_SIZE] __aligned(THREAD_SIZE); -int __init start_uml(void) +void __init start_uml(void) { stack_protections((unsigned long) &cpu_irqstacks[0]); set_sigstack(cpu_irqstacks[0], THREAD_SIZE); @@ -38,32 +36,8 @@ int __init start_uml(void) init_task.thread.request.thread.proc = start_kernel_proc; init_task.thread.request.thread.arg = NULL; - return start_idle_thread(task_stack_page(&init_task), - &init_task.thread.switch_buf); -} - -unsigned long current_stub_stack(void) -{ - if (current->mm == NULL) - return 0; - - return current->mm->context.id.stack; -} - -struct mm_id *current_mm_id(void) -{ - if (current->mm == NULL) - return NULL; - - return ¤t->mm->context.id; -} - -void current_mm_sync(void) -{ - if (current->mm == NULL) - return; - - um_tlb_sync(current->mm); + start_idle_thread(task_stack_page(&init_task), + &init_task.thread.switch_buf); } static DEFINE_SPINLOCK(initial_jmpbuf_spinlock); diff --git a/arch/um/kernel/skas/syscall.c b/arch/um/kernel/skas/syscall.c index bc117733e8ef..914b90af42ed 100644 --- a/arch/um/kernel/skas/syscall.c +++ b/arch/um/kernel/skas/syscall.c @@ -35,12 +35,13 @@ void handle_syscall(struct uml_pt_regs *r) /* * If no time passes, then sched_yield may not actually yield, causing * broken spinlock implementations in userspace (ASAN) to hang for long - * periods of time. + * periods of time. Advance the time-travel clock a bit so sched_yield() + * in a loop (like ping does for small intervals) also works correctly. */ if ((time_travel_mode == TT_MODE_INFCPU || time_travel_mode == TT_MODE_EXTERNAL) && syscall == __NR_sched_yield) - tt_extra_sched_jiffies += 1; + time_travel_ndelay(1000); if (syscall >= 0 && syscall < __NR_syscalls) { unsigned long ret; diff --git a/arch/um/kernel/skas/uaccess.c b/arch/um/kernel/skas/uaccess.c index caef1deef795..f48f0e7c2497 100644 --- a/arch/um/kernel/skas/uaccess.c +++ b/arch/um/kernel/skas/uaccess.c @@ -231,116 +231,90 @@ long strnlen_user(const char __user *str, long len) } EXPORT_SYMBOL(strnlen_user); -/** - * arch_futex_atomic_op_inuser() - Atomic arithmetic operation with constant - * argument and comparison of the previous - * futex value with another constant. - * - * @op: operation to execute - * @oparg: argument to operation - * @oval: old value at uaddr - * @uaddr: pointer to user space address - * - * Return: - * 0 - On success - * -EFAULT - User access resulted in a page fault - * -EAGAIN - Atomic operation was unable to complete due to contention - * -ENOSYS - Operation not supported - */ - int arch_futex_atomic_op_inuser(int op, u32 oparg, int *oval, u32 __user *uaddr) { - int oldval, ret; - struct page *page; unsigned long addr = (unsigned long) uaddr; + u32 *p, oldval, newval; + struct page *page; pte_t *pte; - ret = -EFAULT; if (!access_ok(uaddr, sizeof(*uaddr))) return -EFAULT; - preempt_disable(); - pte = maybe_map(addr, 1); - if (pte == NULL) - goto out_inuser; - - page = pte_page(*pte); -#ifdef CONFIG_64BIT - pagefault_disable(); - addr = (unsigned long) page_address(page) + - (((unsigned long) addr) & ~PAGE_MASK); -#else - addr = (unsigned long) kmap_atomic(page) + - ((unsigned long) addr & ~PAGE_MASK); -#endif - uaddr = (u32 *) addr; - oldval = *uaddr; - - ret = 0; switch (op) { case FUTEX_OP_SET: - *uaddr = oparg; - break; case FUTEX_OP_ADD: - *uaddr += oparg; - break; case FUTEX_OP_OR: - *uaddr |= oparg; - break; case FUTEX_OP_ANDN: - *uaddr &= ~oparg; - break; case FUTEX_OP_XOR: - *uaddr ^= oparg; break; default: - ret = -ENOSYS; + return -ENOSYS; + } + + preempt_disable(); + pte = maybe_map(addr, 1); + if (pte == NULL) { + preempt_enable(); + return -EFAULT; } + + page = pte_page(*pte); #ifdef CONFIG_64BIT - pagefault_enable(); + pagefault_disable(); + p = page_address(page) + (addr & ~PAGE_MASK); #else - kunmap_atomic((void *)addr); + p = kmap_atomic(page) + (addr & ~PAGE_MASK); #endif -out_inuser: - preempt_enable(); + do { + oldval = READ_ONCE(*p); + + switch (op) { + case FUTEX_OP_SET: + newval = oparg; + break; + case FUTEX_OP_ADD: + newval = oldval + oparg; + break; + case FUTEX_OP_OR: + newval = oldval | oparg; + break; + case FUTEX_OP_ANDN: + newval = oldval & ~oparg; + break; + case FUTEX_OP_XOR: + newval = oldval ^ oparg; + break; + } + } while (!try_cmpxchg(p, &oldval, newval)); - if (ret == 0) - *oval = oldval; + *oval = oldval; - return ret; +#ifdef CONFIG_64BIT + pagefault_enable(); +#else + kunmap_atomic((void *)addr); +#endif + preempt_enable(); + return 0; } -EXPORT_SYMBOL(arch_futex_atomic_op_inuser); - -/** - * futex_atomic_cmpxchg_inatomic() - Compare and exchange the content of the - * uaddr with newval if the current value is - * oldval. - * @uval: pointer to store content of @uaddr - * @uaddr: pointer to user space address - * @oldval: old value - * @newval: new value to store to @uaddr - * - * Return: - * 0 - On success - * -EFAULT - User access resulted in a page fault - * -EAGAIN - Atomic operation was unable to complete due to contention - */ int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, - u32 oldval, u32 newval) + u32 oldval, u32 newval) { struct page *page; pte_t *pte; - int ret = -EFAULT; if (!access_ok(uaddr, sizeof(*uaddr))) return -EFAULT; preempt_disable(); pte = maybe_map((unsigned long) uaddr, 1); - if (pte == NULL) - goto out_inatomic; + if (pte == NULL) { + preempt_enable(); + return -EFAULT; + } page = pte_page(*pte); #ifdef CONFIG_64BIT @@ -350,19 +324,13 @@ int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, uaddr = kmap_atomic(page) + ((unsigned long) uaddr & ~PAGE_MASK); #endif - *uval = *uaddr; - - ret = cmpxchg(uaddr, oldval, newval); + *uval = cmpxchg(uaddr, oldval, newval); #ifdef CONFIG_64BIT pagefault_enable(); #else kunmap_atomic(uaddr); #endif - ret = 0; - -out_inatomic: preempt_enable(); - return ret; + return 0; } -EXPORT_SYMBOL(futex_atomic_cmpxchg_inatomic); diff --git a/arch/um/kernel/smp.c b/arch/um/kernel/smp.c index f1e52b7348fb..92ea1beaed8b 100644 --- a/arch/um/kernel/smp.c +++ b/arch/um/kernel/smp.c @@ -20,6 +20,7 @@ #include <init.h> #include <kern.h> #include <os.h> +#include <skas.h> #include <smp.h> enum { @@ -66,7 +67,7 @@ static void ipi_handler(int vector, struct uml_pt_regs *regs) irq_enter(); if (current->mm) - os_alarm_process(current->mm->context.id.pid); + os_alarm_process(current_mm_id()->pid); switch (vector) { case UML_IPI_RES: diff --git a/arch/um/kernel/sysrq.c b/arch/um/kernel/sysrq.c index 13ee5666668d..5a2c28e09294 100644 --- a/arch/um/kernel/sysrq.c +++ b/arch/um/kernel/sysrq.c @@ -18,7 +18,7 @@ static void _print_addr(void *data, unsigned long address, int reliable) { const char *loglvl = data; - printk("%s [<%08lx>] %s%pS\n", loglvl, address, reliable ? "" : "? ", + printk("%s [<%08lx>] %s%pB\n", loglvl, address, reliable ? "" : "? ", (void *)address); } diff --git a/arch/um/kernel/time.c b/arch/um/kernel/time.c index b344a36b44eb..d620b6d6310a 100644 --- a/arch/um/kernel/time.c +++ b/arch/um/kernel/time.c @@ -19,6 +19,7 @@ #include <asm/param.h> #include <kern_util.h> #include <os.h> +#include <skas.h> #include <linux/delay.h> #include <linux/time-internal.h> #include <linux/um_timetravel.h> @@ -873,7 +874,7 @@ static irqreturn_t um_timer(int irq, void *dev) if (time_travel_mode != TT_MODE_INFCPU && time_travel_mode != TT_MODE_EXTERNAL && get_current()->mm) - os_alarm_process(get_current()->mm->context.id.pid); + os_alarm_process(current_mm_id()->pid); evt->event_handler(evt); diff --git a/arch/um/kernel/tlb.c b/arch/um/kernel/tlb.c index 1f175716b474..012bcc76168d 100644 --- a/arch/um/kernel/tlb.c +++ b/arch/um/kernel/tlb.c @@ -40,13 +40,6 @@ static int kern_unmap(struct mm_id *mm_idp, return os_unmap_memory((void *)virt, len); } -void report_enomem(void) -{ - printk(KERN_ERR "UML ran out of memory on the host side! " - "This can happen due to a memory limitation or " - "vm.max_map_count has been reached.\n"); -} - static inline int update_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, struct vm_ops *ops) diff --git a/arch/um/kernel/trap-mmu.c b/arch/um/kernel/trap-mmu.c new file mode 100644 index 000000000000..467c100aafde --- /dev/null +++ b/arch/um/kernel/trap-mmu.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + */ + +#include <linux/mm.h> +#include <linux/sched/signal.h> +#include <linux/hardirq.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/sched/debug.h> +#include <asm/current.h> +#include <asm/tlbflush.h> +#include <arch.h> +#include <as-layout.h> +#include <kern_util.h> +#include <os.h> +#include <skas.h> + +/* + * NOTE: UML does not have exception tables. As such, this is almost a copy + * of the code in mm/memory.c, only adjusting the logic to simply check whether + * we are coming from the kernel instead of doing an additional lookup in the + * exception table. + * We can do this simplification because we never get here if the exception was + * fixable. + */ +static inline bool get_mmap_lock_carefully(struct mm_struct *mm, bool is_user) +{ + if (likely(mmap_read_trylock(mm))) + return true; + + if (!is_user) + return false; + + return !mmap_read_lock_killable(mm); +} + +static inline bool mmap_upgrade_trylock(struct mm_struct *mm) +{ + /* + * We don't have this operation yet. + * + * It should be easy enough to do: it's basically a + * atomic_long_try_cmpxchg_acquire() + * from RWSEM_READER_BIAS -> RWSEM_WRITER_LOCKED, but + * it also needs the proper lockdep magic etc. + */ + return false; +} + +static inline bool upgrade_mmap_lock_carefully(struct mm_struct *mm, bool is_user) +{ + mmap_read_unlock(mm); + if (!is_user) + return false; + + return !mmap_write_lock_killable(mm); +} + +/* + * Helper for page fault handling. + * + * This is kind of equivalend to "mmap_read_lock()" followed + * by "find_extend_vma()", except it's a lot more careful about + * the locking (and will drop the lock on failure). + * + * For example, if we have a kernel bug that causes a page + * fault, we don't want to just use mmap_read_lock() to get + * the mm lock, because that would deadlock if the bug were + * to happen while we're holding the mm lock for writing. + * + * So this checks the exception tables on kernel faults in + * order to only do this all for instructions that are actually + * expected to fault. + * + * We can also actually take the mm lock for writing if we + * need to extend the vma, which helps the VM layer a lot. + */ +static struct vm_area_struct * +um_lock_mm_and_find_vma(struct mm_struct *mm, + unsigned long addr, bool is_user) +{ + struct vm_area_struct *vma; + + if (!get_mmap_lock_carefully(mm, is_user)) + return NULL; + + vma = find_vma(mm, addr); + if (likely(vma && (vma->vm_start <= addr))) + return vma; + + /* + * Well, dang. We might still be successful, but only + * if we can extend a vma to do so. + */ + if (!vma || !(vma->vm_flags & VM_GROWSDOWN)) { + mmap_read_unlock(mm); + return NULL; + } + + /* + * We can try to upgrade the mmap lock atomically, + * in which case we can continue to use the vma + * we already looked up. + * + * Otherwise we'll have to drop the mmap lock and + * re-take it, and also look up the vma again, + * re-checking it. + */ + if (!mmap_upgrade_trylock(mm)) { + if (!upgrade_mmap_lock_carefully(mm, is_user)) + return NULL; + + vma = find_vma(mm, addr); + if (!vma) + goto fail; + if (vma->vm_start <= addr) + goto success; + if (!(vma->vm_flags & VM_GROWSDOWN)) + goto fail; + } + + if (expand_stack_locked(vma, addr)) + goto fail; + +success: + mmap_write_downgrade(mm); + return vma; + +fail: + mmap_write_unlock(mm); + return NULL; +} + +/* + * Note this is constrained to return 0, -EFAULT, -EACCES, -ENOMEM by + * segv(). + */ +int handle_page_fault(unsigned long address, unsigned long ip, + int is_write, int is_user, int *code_out) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + pmd_t *pmd; + pte_t *pte; + int err = -EFAULT; + unsigned int flags = FAULT_FLAG_DEFAULT; + + *code_out = SEGV_MAPERR; + + /* + * If the fault was with pagefaults disabled, don't take the fault, just + * fail. + */ + if (faulthandler_disabled()) + goto out_nosemaphore; + + if (is_user) + flags |= FAULT_FLAG_USER; +retry: + vma = um_lock_mm_and_find_vma(mm, address, is_user); + if (!vma) + goto out_nosemaphore; + + *code_out = SEGV_ACCERR; + if (is_write) { + if (!(vma->vm_flags & VM_WRITE)) + goto out; + flags |= FAULT_FLAG_WRITE; + } else { + /* Don't require VM_READ|VM_EXEC for write faults! */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto out; + } + + do { + vm_fault_t fault; + + fault = handle_mm_fault(vma, address, flags, NULL); + + if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) + goto out_nosemaphore; + + /* The fault is fully completed (including releasing mmap lock) */ + if (fault & VM_FAULT_COMPLETED) + return 0; + + if (unlikely(fault & VM_FAULT_ERROR)) { + if (fault & VM_FAULT_OOM) { + goto out_of_memory; + } else if (fault & VM_FAULT_SIGSEGV) { + goto out; + } else if (fault & VM_FAULT_SIGBUS) { + err = -EACCES; + goto out; + } + BUG(); + } + if (fault & VM_FAULT_RETRY) { + flags |= FAULT_FLAG_TRIED; + + goto retry; + } + + pmd = pmd_off(mm, address); + pte = pte_offset_kernel(pmd, address); + } while (!pte_present(*pte)); + err = 0; + /* + * The below warning was added in place of + * pte_mkyoung(); if (is_write) pte_mkdirty(); + * If it's triggered, we'd see normally a hang here (a clean pte is + * marked read-only to emulate the dirty bit). + * However, the generic code can mark a PTE writable but clean on a + * concurrent read fault, triggering this harmlessly. So comment it out. + */ +#if 0 + WARN_ON(!pte_young(*pte) || (is_write && !pte_dirty(*pte))); +#endif + +out: + mmap_read_unlock(mm); +out_nosemaphore: + return err; + +out_of_memory: + /* + * We ran out of memory, call the OOM killer, and return the userspace + * (which will retry the fault, or kill us if we got oom-killed). + */ + mmap_read_unlock(mm); + if (!is_user) + goto out_nosemaphore; + pagefault_out_of_memory(); + return 0; +} diff --git a/arch/um/kernel/trap-nommu.c b/arch/um/kernel/trap-nommu.c new file mode 100644 index 000000000000..891a9cefa935 --- /dev/null +++ b/arch/um/kernel/trap-nommu.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <shared/kern_util.h> + +int handle_page_fault(unsigned long address, unsigned long ip, + int is_write, int is_user, int *code_out) +{ + /* everything that's valid is already mapped in NOMMU */ + *code_out = SEGV_MAPERR; + return -EFAULT; +} diff --git a/arch/um/kernel/trap.c b/arch/um/kernel/trap.c index 177615820a4c..ab60bab8d987 100644 --- a/arch/um/kernel/trap.c +++ b/arch/um/kernel/trap.c @@ -17,225 +17,6 @@ #include <os.h> #include <skas.h> -/* - * NOTE: UML does not have exception tables. As such, this is almost a copy - * of the code in mm/memory.c, only adjusting the logic to simply check whether - * we are coming from the kernel instead of doing an additional lookup in the - * exception table. - * We can do this simplification because we never get here if the exception was - * fixable. - */ -static inline bool get_mmap_lock_carefully(struct mm_struct *mm, bool is_user) -{ - if (likely(mmap_read_trylock(mm))) - return true; - - if (!is_user) - return false; - - return !mmap_read_lock_killable(mm); -} - -static inline bool mmap_upgrade_trylock(struct mm_struct *mm) -{ - /* - * We don't have this operation yet. - * - * It should be easy enough to do: it's basically a - * atomic_long_try_cmpxchg_acquire() - * from RWSEM_READER_BIAS -> RWSEM_WRITER_LOCKED, but - * it also needs the proper lockdep magic etc. - */ - return false; -} - -static inline bool upgrade_mmap_lock_carefully(struct mm_struct *mm, bool is_user) -{ - mmap_read_unlock(mm); - if (!is_user) - return false; - - return !mmap_write_lock_killable(mm); -} - -/* - * Helper for page fault handling. - * - * This is kind of equivalend to "mmap_read_lock()" followed - * by "find_extend_vma()", except it's a lot more careful about - * the locking (and will drop the lock on failure). - * - * For example, if we have a kernel bug that causes a page - * fault, we don't want to just use mmap_read_lock() to get - * the mm lock, because that would deadlock if the bug were - * to happen while we're holding the mm lock for writing. - * - * So this checks the exception tables on kernel faults in - * order to only do this all for instructions that are actually - * expected to fault. - * - * We can also actually take the mm lock for writing if we - * need to extend the vma, which helps the VM layer a lot. - */ -static struct vm_area_struct * -um_lock_mm_and_find_vma(struct mm_struct *mm, - unsigned long addr, bool is_user) -{ - struct vm_area_struct *vma; - - if (!get_mmap_lock_carefully(mm, is_user)) - return NULL; - - vma = find_vma(mm, addr); - if (likely(vma && (vma->vm_start <= addr))) - return vma; - - /* - * Well, dang. We might still be successful, but only - * if we can extend a vma to do so. - */ - if (!vma || !(vma->vm_flags & VM_GROWSDOWN)) { - mmap_read_unlock(mm); - return NULL; - } - - /* - * We can try to upgrade the mmap lock atomically, - * in which case we can continue to use the vma - * we already looked up. - * - * Otherwise we'll have to drop the mmap lock and - * re-take it, and also look up the vma again, - * re-checking it. - */ - if (!mmap_upgrade_trylock(mm)) { - if (!upgrade_mmap_lock_carefully(mm, is_user)) - return NULL; - - vma = find_vma(mm, addr); - if (!vma) - goto fail; - if (vma->vm_start <= addr) - goto success; - if (!(vma->vm_flags & VM_GROWSDOWN)) - goto fail; - } - - if (expand_stack_locked(vma, addr)) - goto fail; - -success: - mmap_write_downgrade(mm); - return vma; - -fail: - mmap_write_unlock(mm); - return NULL; -} - -/* - * Note this is constrained to return 0, -EFAULT, -EACCES, -ENOMEM by - * segv(). - */ -int handle_page_fault(unsigned long address, unsigned long ip, - int is_write, int is_user, int *code_out) -{ - struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; - pmd_t *pmd; - pte_t *pte; - int err = -EFAULT; - unsigned int flags = FAULT_FLAG_DEFAULT; - - *code_out = SEGV_MAPERR; - - /* - * If the fault was with pagefaults disabled, don't take the fault, just - * fail. - */ - if (faulthandler_disabled()) - goto out_nosemaphore; - - if (is_user) - flags |= FAULT_FLAG_USER; -retry: - vma = um_lock_mm_and_find_vma(mm, address, is_user); - if (!vma) - goto out_nosemaphore; - - *code_out = SEGV_ACCERR; - if (is_write) { - if (!(vma->vm_flags & VM_WRITE)) - goto out; - flags |= FAULT_FLAG_WRITE; - } else { - /* Don't require VM_READ|VM_EXEC for write faults! */ - if (!(vma->vm_flags & (VM_READ | VM_EXEC))) - goto out; - } - - do { - vm_fault_t fault; - - fault = handle_mm_fault(vma, address, flags, NULL); - - if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) - goto out_nosemaphore; - - /* The fault is fully completed (including releasing mmap lock) */ - if (fault & VM_FAULT_COMPLETED) - return 0; - - if (unlikely(fault & VM_FAULT_ERROR)) { - if (fault & VM_FAULT_OOM) { - goto out_of_memory; - } else if (fault & VM_FAULT_SIGSEGV) { - goto out; - } else if (fault & VM_FAULT_SIGBUS) { - err = -EACCES; - goto out; - } - BUG(); - } - if (fault & VM_FAULT_RETRY) { - flags |= FAULT_FLAG_TRIED; - - goto retry; - } - - pmd = pmd_off(mm, address); - pte = pte_offset_kernel(pmd, address); - } while (!pte_present(*pte)); - err = 0; - /* - * The below warning was added in place of - * pte_mkyoung(); if (is_write) pte_mkdirty(); - * If it's triggered, we'd see normally a hang here (a clean pte is - * marked read-only to emulate the dirty bit). - * However, the generic code can mark a PTE writable but clean on a - * concurrent read fault, triggering this harmlessly. So comment it out. - */ -#if 0 - WARN_ON(!pte_young(*pte) || (is_write && !pte_dirty(*pte))); -#endif - -out: - mmap_read_unlock(mm); -out_nosemaphore: - return err; - -out_of_memory: - /* - * We ran out of memory, call the OOM killer, and return the userspace - * (which will retry the fault, or kill us if we got oom-killed). - */ - mmap_read_unlock(mm); - if (!is_user) - goto out_nosemaphore; - pagefault_out_of_memory(); - return 0; -} - static void show_segv_info(struct uml_pt_regs *regs) { struct task_struct *tsk = current; diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c index e4ee693961e4..3dbe3acc4833 100644 --- a/arch/um/kernel/um_arch.c +++ b/arch/um/kernel/um_arch.c @@ -44,7 +44,12 @@ static char __initdata command_line[COMMAND_LINE_SIZE] = { 0 }; static void __init add_arg(char *arg) { - if (strlen(command_line) + strlen(arg) + 1 > COMMAND_LINE_SIZE) { + size_t len = strlen(command_line) + strlen(arg) + 1; + + if (command_line[0]) + len++; + + if (len > COMMAND_LINE_SIZE) { os_warn("add_arg: Too many command line arguments!\n"); exit(1); } @@ -303,7 +308,7 @@ static unsigned long __init get_top_address(char **envp) return PAGE_ALIGN(top_addr + 1); } -int __init linux_main(int argc, char **argv, char **envp) +void __init linux_main(int argc, char **argv, char **envp) { unsigned long avail, diff; unsigned long virtmem_size, max_physmem; @@ -396,7 +401,7 @@ int __init linux_main(int argc, char **argv, char **envp) os_flush_stdout(); - return start_uml(); + start_uml(); } int __init __weak read_initrd(void) diff --git a/arch/um/kernel/uml.lds.S b/arch/um/kernel/uml.lds.S index 30aa24348d60..f6e100143558 100644 --- a/arch/um/kernel/uml.lds.S +++ b/arch/um/kernel/uml.lds.S @@ -28,8 +28,13 @@ SECTIONS . = ALIGN(PAGE_SIZE); _text = .; + __init_begin = .; INIT_TEXT_SECTION(0) + init.data : { INIT_DATA } + . = ALIGN(PAGE_SIZE); + __init_end = .; + .text : { _stext = .; @@ -70,14 +75,11 @@ SECTIONS #include <asm/common.lds.S> - __init_begin = .; - init.data : { INIT_DATA } - __init_end = .; - .data : { INIT_TASK_DATA(KERNEL_STACK_SIZE) DATA_DATA + *(.ldata .ldata.[0-9a-zA-Z_]* .ldata..L*) *(.gnu.linkonce.d*) CONSTRUCTORS } @@ -107,6 +109,7 @@ SECTIONS PROVIDE(_bss_start = .); SBSS(0) BSS(0) + .lbss : { *(.lbss .lbss.[0-9a-zA-Z_]* .lbss..L*) } __bss_stop = .; _end = .; PROVIDE (end = .); diff --git a/arch/um/os-Linux/main.c b/arch/um/os-Linux/main.c index 7e114862a723..ea41188ef2d0 100644 --- a/arch/um/os-Linux/main.c +++ b/arch/um/os-Linux/main.c @@ -100,10 +100,11 @@ static void __init setup_env_path(void) } } +static char **new_argv; + int __init main(int argc, char **argv, char **envp) { - char **new_argv; - int ret, i, err; + int ret, i; /* Disable randomization and re-exec if it was changed successfully */ ret = personality(PER_LINUX | ADDR_NO_RANDOMIZE); @@ -150,7 +151,12 @@ int __init main(int argc, char **argv, char **envp) scan_elf_aux(envp); change_sig(SIGPIPE, 0); - ret = linux_main(argc, argv, envp); + linux_main(argc, argv, envp); +} + +void os_exit(bool reboot) +{ + int err; /* * Disable SIGPROF - I have no idea why libc doesn't do this or turn @@ -183,12 +189,11 @@ int __init main(int argc, char **argv, char **envp) os_info("\n"); /* Reboot */ - if (ret) { + if (reboot) { execvp(new_argv[0], new_argv); perror("Failed to exec kernel"); - ret = 1; } - return uml_exitcode; + exit(uml_exitcode); } extern void *__real_malloc(int); diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c index 6c993bc8c78e..57f0a5e46afb 100644 --- a/arch/um/os-Linux/signal.c +++ b/arch/um/os-Linux/signal.c @@ -453,7 +453,7 @@ void unblock_signals_hard(void) * 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 + * signal handler 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. diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c index d6c22f8aa06d..bca05a5a7a83 100644 --- a/arch/um/os-Linux/skas/process.c +++ b/arch/um/os-Linux/skas/process.c @@ -816,7 +816,7 @@ 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) +void start_idle_thread(void *stack, jmp_buf *switch_buf) { int n; @@ -843,21 +843,16 @@ int start_idle_thread(void *stack, jmp_buf *switch_buf) break; case INIT_JMP_HALT: kmalloc_ok = 0; - return 0; + os_exit(false); case INIT_JMP_REBOOT: kmalloc_ok = 0; - return 1; + os_exit(true); 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) diff --git a/arch/x86/um/Makefile b/arch/x86/um/Makefile index f9ea75bf43ac..100b68f601f9 100644 --- a/arch/x86/um/Makefile +++ b/arch/x86/um/Makefile @@ -47,4 +47,7 @@ include/generated/user_constants.h: $(obj)/user-offsets.s FORCE UNPROFILE_OBJS := stub_segv.o CFLAGS_stub_segv.o := $(CFLAGS_NO_HARDENING) +# see arch/um/kernel/skas/Makefile - this goes into the stub +CFLAGS_stub_segv.o += $(call cc-option, -ftrivial-auto-var-init=uninitialized) + include $(srctree)/arch/um/scripts/Makefile.rules diff --git a/arch/x86/um/asm/elf.h b/arch/x86/um/asm/elf.h index 22d0111b543b..388fe669886c 100644 --- a/arch/x86/um/asm/elf.h +++ b/arch/x86/um/asm/elf.h @@ -9,6 +9,7 @@ #include <skas.h> #define CORE_DUMP_USE_REGSET +#define ELF_FDPIC_CORE_EFLAGS 0 #ifdef CONFIG_X86_32 @@ -158,8 +159,11 @@ extern int arch_setup_additional_pages(struct linux_binprm *bprm, extern unsigned long um_vdso_addr; #define AT_SYSINFO_EHDR 33 -#define ARCH_DLINFO NEW_AUX_ENT(AT_SYSINFO_EHDR, um_vdso_addr) - +#define ARCH_DLINFO \ +do { \ + NEW_AUX_ENT(AT_SYSINFO_EHDR, um_vdso_addr); \ + NEW_AUX_ENT(AT_MINSIGSTKSZ, 0); \ +} while (0) #endif typedef unsigned long elf_greg_t; diff --git a/arch/x86/um/asm/required-features.h b/arch/x86/um/asm/required-features.h deleted file mode 100644 index dfb967b2d2f3..000000000000 --- a/arch/x86/um/asm/required-features.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __UM_REQUIRED_FEATURES_H -#define __UM_REQUIRED_FEATURES_H - -/* - * Nothing to see, just need something for the i386 and x86_64 asm - * headers to include. - */ - -#endif diff --git a/arch/x86/um/vdso/vma.c b/arch/x86/um/vdso/vma.c index 51a2b9f2eca9..8e1f5ee44f4e 100644 --- a/arch/x86/um/vdso/vma.c +++ b/arch/x86/um/vdso/vma.c @@ -9,6 +9,7 @@ #include <asm/page.h> #include <asm/elf.h> #include <linux/init.h> +#include <os.h> unsigned long um_vdso_addr; static struct page *um_vdso; @@ -20,18 +21,25 @@ static int __init init_vdso(void) { BUG_ON(vdso_end - vdso_start > PAGE_SIZE); - um_vdso_addr = task_size - PAGE_SIZE; - um_vdso = alloc_page(GFP_KERNEL); if (!um_vdso) panic("Cannot allocate vdso\n"); copy_page(page_address(um_vdso), vdso_start); +#ifdef CONFIG_MMU + um_vdso_addr = task_size - PAGE_SIZE; +#else + /* this is fine with NOMMU as everything is accessible */ + um_vdso_addr = (unsigned long)page_address(um_vdso); + os_protect_memory((void *)um_vdso_addr, vdso_end - vdso_start, 1, 0, 1); +#endif + return 0; } subsys_initcall(init_vdso); +#ifdef CONFIG_MMU int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) { struct vm_area_struct *vma; @@ -53,3 +61,4 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) return IS_ERR(vma) ? PTR_ERR(vma) : 0; } +#endif /* CONFIG_MMU */ diff --git a/fs/Kconfig.binfmt b/fs/Kconfig.binfmt index daeac4889d03..e36cd94513b9 100644 --- a/fs/Kconfig.binfmt +++ b/fs/Kconfig.binfmt @@ -58,7 +58,7 @@ config ARCH_USE_GNU_PROPERTY config BINFMT_ELF_FDPIC bool "Kernel support for FDPIC ELF binaries" default y if !BINFMT_ELF - depends on ARM || ((M68K || RISCV || SUPERH || XTENSA) && !MMU) + depends on ARM || ((M68K || RISCV || SUPERH || UML || XTENSA) && !MMU) select ELFCORE help ELF FDPIC binaries are based on ELF, but allow the individual load diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index f28d8990d2bb..4dd9649fa049 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -649,7 +649,7 @@ menu "Generic Kernel Debugging Instruments" config MAGIC_SYSRQ bool "Magic SysRq key" - depends on !UML + depends on !UML || MCONSOLE help If you say Y here, you will have some control over the system even if the system crashes for example during kernel debugging (e.g., you @@ -657,9 +657,11 @@ config MAGIC_SYSRQ immediately or dump some status information). This is accomplished by pressing various keys while holding SysRq (Alt+PrintScreen). It also works on a serial console (on PC hardware at least), if you - send a BREAK and then within 5 seconds a command keypress. The - keys are documented in <file:Documentation/admin-guide/sysrq.rst>. - Don't say Y unless you really know what this hack does. + send a BREAK and then within 5 seconds a command keypress. On UML, + this is accomplished by sending a "sysrq" command with mconsole, + followed by the letter for the requested command. The keys are + documented in <file:Documentation/admin-guide/sysrq.rst>. Don't say + Y unless you really know what this hack does. config MAGIC_SYSRQ_DEFAULT_ENABLE hex "Enable magic SysRq key functions by default" diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig index 94ff8e4089bf..cbda6189a308 100644 --- a/lib/kunit/Kconfig +++ b/lib/kunit/Kconfig @@ -133,7 +133,7 @@ config KUNIT_DEFAULT_TIMEOUT config KUNIT_UML_PCI bool "KUnit UML PCI Support" - depends on UML + depends on UML && MMU select UML_PCI help Enables the PCI subsystem on UML for use by KUnit tests. |
