diff --git a/examples/pffault/CMakeLists.txt b/examples/pffault/CMakeLists.txt new file mode 100644 index 00000000000..5352cab0d1f --- /dev/null +++ b/examples/pffault/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# apps/examples/pffault/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_EXAMPLES_PFFAULT) + nuttx_add_application( + NAME + ${CONFIG_EXAMPLES_PFFAULT_PROGNAME} + SRCS + pffault_main.c + STACKSIZE + ${CONFIG_EXAMPLES_PFFAULT_STACKSIZE} + PRIORITY + ${CONFIG_EXAMPLES_PFFAULT_PRIORITY}) +endif() diff --git a/examples/pffault/Kconfig b/examples/pffault/Kconfig new file mode 100644 index 00000000000..92a7ad94324 --- /dev/null +++ b/examples/pffault/Kconfig @@ -0,0 +1,28 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_PFFAULT + tristate "Page-fault / PMS isolation test" + default n + ---help--- + A user-space task that deliberately touches a kernel-space address to + exercise the ESP32-S3 PMS isolation boundary and the kernel's + recoverable-fault dispatcher. + +if EXAMPLES_PFFAULT + +config EXAMPLES_PFFAULT_PROGNAME + string "Program name" + default "pffault" + +config EXAMPLES_PFFAULT_PRIORITY + int "pffault task priority" + default 100 + +config EXAMPLES_PFFAULT_STACKSIZE + int "pffault stack size" + default DEFAULT_TASK_STACKSIZE + +endif diff --git a/examples/pffault/Make.defs b/examples/pffault/Make.defs new file mode 100644 index 00000000000..85e12bb87fb --- /dev/null +++ b/examples/pffault/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/examples/pffault/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_PFFAULT),) +CONFIGURED_APPS += $(APPDIR)/examples/pffault +endif diff --git a/examples/pffault/Makefile b/examples/pffault/Makefile new file mode 100644 index 00000000000..ab3cb25f414 --- /dev/null +++ b/examples/pffault/Makefile @@ -0,0 +1,32 @@ +############################################################################ +# apps/examples/pffault/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_EXAMPLES_PFFAULT_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_PFFAULT_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_PFFAULT_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_PFFAULT) + +MAINSRC = pffault_main.c + +include $(APPDIR)/Application.mk diff --git a/examples/pffault/pffault_main.c b/examples/pffault/pffault_main.c new file mode 100644 index 00000000000..ec90efcca71 --- /dev/null +++ b/examples/pffault/pffault_main.c @@ -0,0 +1,81 @@ +/**************************************************************************** + * apps/examples/pffault/pffault_main.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: main + * + * Description: + * Deliberately touch a kernel-space address from an unprivileged (WORLD1) + * user task to exercise the ESP32-S3 PMS isolation boundary. In a working + * protected build this raises a precise Load/StoreProhibited fault that + * the kernel's recoverable-fault dispatcher must handle (terminating just + * this task); the shell should survive. + * + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + /* Default target: the base of the kernel DRAM region, to which the user + * world has no PMS permission. A second argument "w" makes it a store. + */ + + volatile uint32_t *kaddr = (volatile uint32_t *)0x3fc98000; + bool store = (argc > 1 && argv[1][0] == 'w'); + + if (argc > 2) + { + kaddr = (volatile uint32_t *)strtoul(argv[2], NULL, 0); + } + + printf("pffault: user-space %s of kernel addr %p ...\n", + store ? "write" : "read", (void *)kaddr); + fflush(stdout); + + if (store) + { + *kaddr = 0xdeadbeef; /* Expect a precise StoreProhibited (WORLD1) */ + } + else + { + uint32_t v = *kaddr; /* Expect a precise LoadProhibited (WORLD1) */ + printf("pffault: SURVIVED unexpectedly, read %08lx\n", + (unsigned long)v); + } + + printf("pffault: returned from the faulting access (unexpected)\n"); + return 0; +} diff --git a/examples/sandbox/CMakeLists.txt b/examples/sandbox/CMakeLists.txt new file mode 100644 index 00000000000..10d902065f6 --- /dev/null +++ b/examples/sandbox/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# apps/examples/sandbox/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_EXAMPLES_SANDBOX) + nuttx_add_application( + NAME + ${CONFIG_EXAMPLES_SANDBOX_PROGNAME} + SRCS + sandbox_main.c + STACKSIZE + ${CONFIG_EXAMPLES_SANDBOX_STACKSIZE} + PRIORITY + ${CONFIG_EXAMPLES_SANDBOX_PRIORITY}) +endif() diff --git a/examples/sandbox/Kconfig b/examples/sandbox/Kconfig new file mode 100644 index 00000000000..1da91d00380 --- /dev/null +++ b/examples/sandbox/Kconfig @@ -0,0 +1,85 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_SANDBOX + tristate "Protected-build sandbox containment test" + default n + ---help--- + A test that deliberately tries to escape the kernel/user boundary of + a protected or kernel build, and checks that the attempt is contained: + the offending process is terminated and everything else keeps running. + + Each target carries the outcome it expects, so the test fails a build + that refuses everything as well as one that permits everything. The + "self" target is the control: it touches memory the process owns and + must be allowed. + + A protected build derives the kernel target from CONFIG_NUTTX_USERSPACE. + A kernel build has no such address, because every process is loaded + into its own address environment, so the addresses below supply it. + +if EXAMPLES_SANDBOX + +config EXAMPLES_SANDBOX_PROGNAME + string "Program name" + default "sandbox" + +config EXAMPLES_SANDBOX_PRIORITY + int "sandbox task priority" + default 100 + +config EXAMPLES_SANDBOX_STACKSIZE + int "sandbox stack size" + default DEFAULT_TASK_STACKSIZE + +config EXAMPLES_SANDBOX_ALLOC + int "Bytes the offender holds when it dies" + default 65536 + ---help--- + The offending process allocates this much, writes to all of it so the + pages are really committed, and opens a file, before it makes the bad + access. It still holds both when it is killed. + + A process that dies owning nothing proves nothing about whether the + kill leaks. Compare "free" before and after a run: the kernel heap + and the page pool both have to come back to where they started. + +config EXAMPLES_SANDBOX_KERNEL_ADDR + hex "Address of kernel memory" + default 0x0 + ---help--- + An address that is mapped and belongs to the kernel. Reading it from + a user process must fault. + + It has to be mapped. An unmapped address tests the absence of a + mapping instead of the permission on one, which is a different thing; + use the unmapped target for that. + + Zero means the target is unavailable, and the test reports it as such. + A protected build may leave this at zero, because CONFIG_NUTTX_USERSPACE + gives the boundary. + +config EXAMPLES_SANDBOX_PERIPH_ADDR + hex "Address of a peripheral register" + default 0x0 + ---help--- + A peripheral register that a user process must not reach, such as the + registers that control the memory mapping itself. + + An MMU keeps processes apart but does not stop one from reaching a + peripheral, so this target exercises a different mechanism from the + kernel target. Zero means the target is unavailable. + +config EXAMPLES_SANDBOX_UNMAPPED_ADDR + hex "Address with no mapping" + default 0x0 + ---help--- + An address in no mapping at all. Touching it must be reported. + + Hardware that answers an unmapped access quietly, with zero for a read + and no fault, hides errors that a fault would show. Zero means the + target is unavailable. + +endif diff --git a/examples/sandbox/Make.defs b/examples/sandbox/Make.defs new file mode 100644 index 00000000000..5c315ca833e --- /dev/null +++ b/examples/sandbox/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/examples/sandbox/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_SANDBOX),) +CONFIGURED_APPS += $(APPDIR)/examples/sandbox +endif diff --git a/examples/sandbox/Makefile b/examples/sandbox/Makefile new file mode 100644 index 00000000000..861baf54599 --- /dev/null +++ b/examples/sandbox/Makefile @@ -0,0 +1,32 @@ +############################################################################ +# apps/examples/sandbox/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_EXAMPLES_SANDBOX_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_SANDBOX_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_SANDBOX_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_SANDBOX) + +MAINSRC = sandbox_main.c + +include $(APPDIR)/Application.mk diff --git a/examples/sandbox/sandbox_main.c b/examples/sandbox/sandbox_main.c new file mode 100644 index 00000000000..e6b1188e817 --- /dev/null +++ b/examples/sandbox/sandbox_main.c @@ -0,0 +1,729 @@ +/**************************************************************************** + * apps/examples/sandbox/sandbox_main.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Named targets. + * + * A bare address says nothing about what should happen when it is touched, + * so every target carries the outcome it expects. A build that refuses + * everything is as broken as one that permits everything, and only the + * "self" case can tell the two apart. + * + * The addresses come from Kconfig because a user process cannot see kernel + * symbols; that is the boundary under test. A board supplies them in its + * defconfig. A target with no address is reported as unavailable rather + * than silently skipped. + */ + +#define SANDBOX_KERNEL_ADDR CONFIG_EXAMPLES_SANDBOX_KERNEL_ADDR +#define SANDBOX_PERIPH_ADDR CONFIG_EXAMPLES_SANDBOX_PERIPH_ADDR +#define SANDBOX_UNMAPPED_ADDR CONFIG_EXAMPLES_SANDBOX_UNMAPPED_ADDR + +/* A protected build knows where the kernel ends without being told: the + * kernel blob is placed below the user blob and the boundary is exactly + * CONFIG_NUTTX_USERSPACE. A kernel build has no such address -- each + * process is loaded into its own address environment -- so there the + * Kconfig value is the only source. + */ + +#if SANDBOX_KERNEL_ADDR == 0 && defined(CONFIG_NUTTX_USERSPACE) +# undef SANDBOX_KERNEL_ADDR +# define SANDBOX_KERNEL_ADDR ((uintptr_t)CONFIG_NUTTX_USERSPACE - 16) +#endif + +#define CANARY_INTERVAL_US 10000 +#define SETTLE_US 200000 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum sandbox_expect_e +{ + EXPECT_FAULT = 0, /* The access must be refused */ + EXPECT_OK /* The access must be allowed */ +}; + +struct sandbox_target_s +{ + FAR const char *name; + uintptr_t addr; + enum sandbox_expect_e expect; + FAR const char *what; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Touched by the "self" target. It belongs to this process, so refusing it + * means the boundary is drawn in the wrong place. + */ + +static volatile uint32_t g_own_word = 0x600df00d; + +/* Bumped by the canary thread. It is the evidence that the system kept + * running while the offender was killed: a kernel that panicked stops + * printing, and one that merely wedged leaves this standing still. + */ + +static volatile unsigned long g_canary; +static volatile bool g_canary_stop; + +/* Sampled by the canary thread while the offender is alive. Taking these + * here lets waitpid() stay blocking, so the exit status is the real one. + */ + +static volatile pid_t g_watch_pid; +static volatile unsigned long g_peak_mem; +static volatile int g_peak_fds; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pool_used + * + * Description: + * The bytes in use, read from /proc/meminfo. The page pool is reported + * when there is one, because that is where the memory of a process comes + * from; otherwise the kernel heap is. + * + * This is what makes the leak check mean something. A count that does not + * move while the offender is alive would measure nothing, and "the same + * before and after" would say nothing about whether the memory came back. + * + * Returned Value: + * The bytes in use, or 0 if /proc/meminfo cannot be read. + * + ****************************************************************************/ + +static unsigned long pool_used(void) +{ + char buf[512]; + FAR char *line; + FAR char *save; + unsigned long kmem = 0; + unsigned long page = 0; + int fd; + int n; + + fd = open("/proc/meminfo", O_RDONLY); + if (fd < 0) + { + return 0; + } + + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + + if (n <= 0) + { + return 0; + } + + buf[n] = '\0'; + + for (line = strtok_r(buf, "\n", &save); line != NULL; + line = strtok_r(NULL, "\n", &save)) + { + FAR char *p = line; + FAR char *end; + unsigned long used; + + /* Every line is " ... ". Read the two + * numbers with strtoul() rather than a scanset, which is not in every + * sscanf(). + */ + + while (*p == ' ') + { + p++; + } + + strtoul(p, &end, 10); + if (end == p) + { + continue; /* The header line. */ + } + + p = end; + while (*p == ' ') + { + p++; + } + + used = strtoul(p, &end, 10); + if (end == p) + { + continue; + } + + if (strstr(line, "Page") != NULL) + { + page = used; + } + else if (strstr(line, "Kmem") != NULL || strstr(line, "Umem") != NULL) + { + kmem = used; + } + } + + return page != 0 ? page : kmem; +} + +/**************************************************************************** + * Name: count_fds + * + * Description: + * The descriptors a process holds, read from /proc//group/fd. The + * first line of that file is a header and is not counted. + * + * Returned Value: + * The number of open descriptors, or -1 when the process is gone, which is + * the evidence that its group was destroyed and not merely emptied. + * + ****************************************************************************/ + +static int count_fds(pid_t pid) +{ + char path[32]; + char buf[512]; + int count = 0; + int fd; + int n; + int i; + + snprintf(path, sizeof(path), "/proc/%d/group/fd", (int)pid); + + fd = open(path, O_RDONLY); + if (fd < 0) + { + return -1; + } + + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + + if (n <= 0) + { + return 0; + } + + buf[n] = '\0'; + + /* Count the lines that start with a digit, which are the descriptors. The + * header starts with "FD". + */ + + for (i = 0; i < n; i++) + { + if ((i == 0 || buf[i - 1] == '\n') && buf[i] >= '0' && buf[i] <= '9') + { + count++; + } + } + + return count; +} + +static FAR void *canary_thread(FAR void *arg) +{ + while (!g_canary_stop) + { + g_canary++; + + if (g_watch_pid > 0) + { + unsigned long mem = pool_used(); + int fds = count_fds(g_watch_pid); + + if (mem > g_peak_mem) + { + g_peak_mem = mem; + } + + if (fds > g_peak_fds) + { + g_peak_fds = fds; + } + } + + usleep(CANARY_INTERVAL_US); + } + + return NULL; +} + +/**************************************************************************** + * Name: resolve + * + * Description: + * Turn a target name, or a literal address, into an address and the + * outcome that address must produce. + * + * Returned Value: + * OK on success, ERROR if the name is unknown or has no address on this + * configuration. + * + ****************************************************************************/ + +static int resolve(FAR const char *name, FAR struct sandbox_target_s *t) +{ + t->name = name; + + if (strcmp(name, "self") == 0) + { + t->addr = (uintptr_t)&g_own_word; + t->expect = EXPECT_OK; + t->what = "this process's own data"; + return OK; + } + + if (strcmp(name, "kernel") == 0) + { + t->addr = SANDBOX_KERNEL_ADDR; + t->expect = EXPECT_FAULT; + t->what = "kernel memory"; + } + else if (strcmp(name, "periph") == 0) + { + t->addr = SANDBOX_PERIPH_ADDR; + t->expect = EXPECT_FAULT; + t->what = "a peripheral register"; + } + else if (strcmp(name, "unmapped") == 0) + { + t->addr = SANDBOX_UNMAPPED_ADDR; + t->expect = EXPECT_FAULT; + t->what = "an address with no mapping"; + } + else if (name[0] == '0' && (name[1] == 'x' || name[1] == 'X')) + { + t->addr = (uintptr_t)strtoul(name, NULL, 0); + t->expect = EXPECT_FAULT; + t->what = "a literal address"; + } + else + { + printf("sandbox: unknown target \"%s\"\n", name); + return ERROR; + } + + if (t->addr == 0) + { + printf("sandbox: target \"%s\" has no address here.\n", name); + printf("sandbox: set CONFIG_EXAMPLES_SANDBOX_%s_ADDR, or pass an " + "address.\n", + strcmp(name, "kernel") == 0 ? "KERNEL" : + strcmp(name, "periph") == 0 ? "PERIPH" : "UNMAPPED"); + return ERROR; + } + + return OK; +} + +/**************************************************************************** + * Name: touch + * + * Description: + * Make the access. Where it is refused this does not return. + * + ****************************************************************************/ + +static void touch(uintptr_t addr, bool store) +{ + FAR volatile uint32_t *p = (FAR volatile uint32_t *)addr; + + printf("sandbox: attempting %s of %p\n", store ? "write" : "read", + (FAR void *)addr); + fflush(stdout); + + if (store) + { + *p = 0xdeadbeef; + } + else + { + uint32_t v = *p; + + printf("sandbox: the read completed and returned %08lx\n", + (unsigned long)v); + fflush(stdout); + return; + } + + printf("sandbox: the write completed\n"); + fflush(stdout); +} + +/**************************************************************************** + * Name: run_case + * + * Description: + * Spawn this program again as a separate process, in "escape" mode, and + * watch what happens to it and to everything else. + * + * The offender has to be a process and not a task. A kernel build does + * not give user code task_create(); making a process is the only way a + * user program can put the bad access somewhere it can be killed. + * + ****************************************************************************/ + +static int run_case(FAR const char *progname, + FAR const struct sandbox_target_s *t, bool store) +{ + FAR char *argv[5]; + char addrbuf[24]; + unsigned long canary_before; + unsigned long canary_after; + unsigned long mem_before; + unsigned long mem_during = 0; + unsigned long mem_after; + int fd_during = -1; + pthread_t canary; + pid_t pid; + int status = 0; + int fails = 0; + int ret; + + printf("\nsandbox: target %s -- %s at %p, expecting %s\n", + t->name, t->what, (FAR void *)t->addr, + t->expect == EXPECT_OK ? "success" : "a fault"); + + g_canary = 0; + g_canary_stop = false; + g_watch_pid = 0; + g_peak_mem = 0; + g_peak_fds = -1; + + if (pthread_create(&canary, NULL, canary_thread, NULL) != 0) + { + printf("sandbox: FAIL - could not start the canary\n"); + return 1; + } + + usleep(SETTLE_US / 2); + canary_before = g_canary; + mem_before = pool_used(); + + snprintf(addrbuf, sizeof(addrbuf), "0x%lx", (unsigned long)t->addr); + argv[0] = (FAR char *)progname; + argv[1] = (FAR char *)"escape"; + argv[2] = store ? (FAR char *)"w" : (FAR char *)"r"; + argv[3] = addrbuf; + argv[4] = NULL; + + ret = posix_spawn(&pid, progname, NULL, NULL, argv, NULL); + if (ret != 0) + { + printf("sandbox: FAIL - could not spawn the offender (%d)\n", ret); + g_canary_stop = true; + pthread_join(canary, NULL); + return 1; + } + + /* Watch while the offender lives. The count has to be seen to rise here, + * or "the same before and after" says nothing at all. If the system + * panics instead of containing the fault, nothing below prints, which is + * itself the result. + */ + + g_watch_pid = pid; + + if (waitpid(pid, &status, 0) < 0) + { + printf("sandbox: FAIL - could not wait for the offender\n"); + fails++; + } + + g_watch_pid = 0; + mem_during = g_peak_mem; + fd_during = g_peak_fds; + + printf("sandbox: the offender exited with status %d\n", status); + + usleep(SETTLE_US); + canary_after = g_canary; + mem_after = pool_used(); + g_canary_stop = true; + pthread_join(canary, NULL); + + printf("sandbox: --- %s ---\n", t->name); + + if (t->expect == EXPECT_OK) + { + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + { + printf("sandbox: PASS - the allowed access completed\n"); + } + else + { + printf("sandbox: FAIL - an owned access was refused\n"); + fails++; + } + } + else if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + { + printf("sandbox: FAIL - NOT CONTAINED, the access was permitted\n"); + fails++; + } + else + { + printf("sandbox: PASS - the offending process was terminated\n"); + } + + printf("sandbox: PASS - this process survived\n"); + + if (canary_after > canary_before) + { + printf("sandbox: PASS - another thread ran (%lu -> %lu)\n", + canary_before, canary_after); + } + else + { + printf("sandbox: FAIL - another thread stopped (%lu -> %lu)\n", + canary_before, canary_after); + fails++; + } + + /* Memory: before, during, after. */ + + printf("sandbox: memory %lu -> %lu -> %lu\n", + mem_before, mem_during, mem_after); + + if (mem_during <= mem_before) + { + printf("sandbox: FAIL - the memory of the offender was never seen\n"); + fails++; + } + else if (mem_after > mem_before) + { + printf("sandbox: FAIL - %lu bytes were not given back\n", + mem_after - mem_before); + fails++; + } + else + { + printf("sandbox: PASS - %lu bytes taken and given back\n", + mem_during - mem_before); + } + + /* Descriptors: open while it lived, and the group gone after. */ + + if (fd_during <= 0) + { + printf("sandbox: FAIL - the descriptors of the offender were never " + "seen\n"); + fails++; + } + else if (count_fds(pid) >= 0) + { + printf("sandbox: FAIL - %d descriptor(s) are still open\n", + count_fds(pid)); + fails++; + } + else + { + printf("sandbox: PASS - %d descriptor(s) open, none after\n", + fd_during); + } + + return fails; +} + +static void usage(FAR const char *progname) +{ + printf("Usage: %s [r|w] [target ...]\n", progname); + printf(" %s escape \n", progname); + printf("\n"); + printf("Targets:\n"); + printf(" self this process's own data must succeed\n"); + printf(" kernel kernel memory must fault\n"); + printf(" periph a peripheral register must fault\n"); + printf(" unmapped an address with no mapping must fault\n"); + printf(" 0x... a literal address must fault\n"); + printf("\n"); + printf("With no target, self, kernel, periph and unmapped are all run.\n"); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + FAR const char *defaults[] = + { + "self", "kernel", "periph", "unmapped" + }; + + struct sandbox_target_s t; + FAR const char *progname = argv[0]; + char raw[256]; + int rawfd; + int rawn; + bool store = false; + int fails = 0; + int ran = 0; + int i; + + if (argc > 1 && strcmp(argv[1], "-h") == 0) + { + usage(progname); + return 0; + } + + /* "escape" is how this program re-enters itself as the offender. It makes + * the access in this process and, where the access is refused, never gets + * to the line below. + */ + + if (argc > 3 && strcmp(argv[1], "escape") == 0) + { + /* Take resources the kernel has to reclaim, and hold them across the + * access. A process that dies owning nothing says nothing about + * whether killing it leaks: the heap block is touched so its pages + * are really committed, and the descriptor is left open on purpose. + */ + + FAR void *mem = malloc(CONFIG_EXAMPLES_SANDBOX_ALLOC); + int fd = open("/system/bin/sandbox", O_RDONLY); + + if (mem != NULL) + { + memset(mem, 0xa5, CONFIG_EXAMPLES_SANDBOX_ALLOC); + } + + printf("sandbox: holding %d bytes at %p and fd %d\n", + CONFIG_EXAMPLES_SANDBOX_ALLOC, mem, fd); + fflush(stdout); + + touch((uintptr_t)strtoul(argv[3], NULL, 0), argv[2][0] == 'w'); + + /* Only an allowed access arrives here. Leave both outstanding, so + * that the normal exit path is measured the same way as the kill. + */ + + return 0; + } + + /* Show where the numbers below come from. */ + + rawfd = open("/proc/meminfo", O_RDONLY); + if (rawfd < 0) + { + printf("sandbox: /proc/meminfo cannot be opened (%d)\n", errno); + } + else + { + rawn = read(rawfd, raw, sizeof(raw) - 1); + close(rawfd); + + if (rawn > 0) + { + raw[rawn] = '\0'; + printf("sandbox: /proc/meminfo reads\n%s", raw); + } + else + { + printf("sandbox: /proc/meminfo read gave %d\n", rawn); + } + } + + i = 1; + if (argc > 1 && (argv[1][0] == 'r' || argv[1][0] == 'w') && + argv[1][1] == '\0') + { + store = (argv[1][0] == 'w'); + i++; + } + + if (i >= argc) + { + int n; + + for (n = 0; n < (int)(sizeof(defaults) / sizeof(defaults[0])); n++) + { + if (resolve(defaults[n], &t) == OK) + { + fails += run_case(progname, &t, store); + ran++; + } + } + } + else + { + for (; i < argc; i++) + { + if (resolve(argv[i], &t) == OK) + { + fails += run_case(progname, &t, store); + ran++; + } + } + } + + printf("\n"); + if (ran == 0) + { + printf("sandbox: no target could be resolved, nothing was tested\n"); + return 1; + } + + if (fails == 0) + { + printf("sandbox: CONTAINED - %d target(s), every check passed\n", ran); + } + else + { + printf("sandbox: NOT CONTAINED - %d of %d target(s) failed\n", + fails, ran); + } + + return fails; +} diff --git a/interpreters/python/Makefile b/interpreters/python/Makefile index bac4013f886..8909586cc30 100644 --- a/interpreters/python/Makefile +++ b/interpreters/python/Makefile @@ -114,6 +114,11 @@ ifeq ($(CONFIG_ARCH_HAVE_FORK),y) else @echo "export ac_cv_func_fork=\"no\"" >> $@ endif +ifneq ($(CONFIG_ARCH_HAVE_VFORK),) + @echo "export ac_cv_func_vfork=\"yes\"" >> $@ +else + @echo "export ac_cv_func_vfork=\"no\"" >> $@ +endif ifeq ($(CONFIG_SYSTEM_SYSTEM),y) @echo "export ac_cv_func_system=\"yes\"" >> $@ else @@ -135,7 +140,10 @@ endif $(SETUP_LOCAL): $(Q) ( cp $(SETUP_LOCAL).in $(SETUP_LOCAL)) -ifneq ($(CONFIG_ARCH_HAVE_FORK),y) +# _posixsubprocess is the fork-then-exec path, so vfork() is enough for it; +# os.fork() itself needs a real fork() and is governed by ac_cv_func_fork +# above. +ifeq ($(CONFIG_ARCH_HAVE_FORK)$(CONFIG_ARCH_HAVE_VFORK),) @echo "_posixsubprocess" >> $@ endif ifneq ($(CONFIG_LIBC_DLFCN),y) diff --git a/netutils/libwebsockets/lws_config_private.h b/netutils/libwebsockets/lws_config_private.h index 8f5170faff9..057baf73bb6 100644 --- a/netutils/libwebsockets/lws_config_private.h +++ b/netutils/libwebsockets/lws_config_private.h @@ -43,7 +43,10 @@ /* #undef USE_CYASSL */ /* Define to 1 if you have the `fork' function. */ + +#ifdef CONFIG_ARCH_HAVE_FORK #define LWS_HAVE_FORK +#endif #ifndef CONFIG_DISABLE_ENVIRON /* Define to 1 if you have the `getenv' function. */ @@ -111,10 +114,14 @@ /* #undef LWS_HAVE_VFORK_H */ /* Define to 1 if `fork' works. */ +#ifdef CONFIG_ARCH_HAVE_FORK #define LWS_HAVE_WORKING_FORK +#endif /* Define to 1 if `vfork' works. */ +#ifdef CONFIG_ARCH_HAVE_VFORK #define LWS_HAVE_WORKING_VFORK +#endif /* Define to 1 if execvpe() exists */ #define LWS_HAVE_EXECVPE diff --git a/testing/fs/fdsantest/fdsantest_simple.c b/testing/fs/fdsantest/fdsantest_simple.c index 128e2878bff..114f0aeeb82 100644 --- a/testing/fs/fdsantest/fdsantest_simple.c +++ b/testing/fs/fdsantest/fdsantest_simple.c @@ -96,6 +96,7 @@ static void test_case_overflow(void **state) assert_int_equal(open_count, close_count); } +#ifdef CONFIG_ARCH_HAVE_VFORK static void test_case_vfork(void **state) { int fd = open("/dev/null", O_RDONLY); @@ -112,6 +113,7 @@ static void test_case_vfork(void **state) android_fdsan_close_with_tag(fd, 0xbadc0de); } +#endif /**************************************************************************** * Public Functions @@ -129,7 +131,9 @@ int main(int argc, FAR char *argv[]) cmocka_unit_test(test_case_unowned_tagged_close), cmocka_unit_test(test_case_owned_tagged_close), cmocka_unit_test(test_case_overflow), +#ifdef CONFIG_ARCH_HAVE_VFORK cmocka_unit_test(test_case_vfork), +#endif }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/testing/ltp/Makefile b/testing/ltp/Makefile index cee750cd0a1..9a2513336d6 100644 --- a/testing/ltp/Makefile +++ b/testing/ltp/Makefile @@ -45,7 +45,7 @@ BLACKWORDS += "pthread_spin_trylock" endif # Where NuttX does not declare fork(), a test that calls it cannot be built. -# The pattern spares vfork() and task_fork(), which remain available. +# The pattern spares vfork(), and any identifier that ends in _fork(). ifeq ($(CONFIG_ARCH_HAVE_FORK),) BLACKWORDS += "[^v_]fork(" diff --git a/testing/ostest/CMakeLists.txt b/testing/ostest/CMakeLists.txt index a76b2447c00..8eb6516790e 100644 --- a/testing/ostest/CMakeLists.txt +++ b/testing/ostest/CMakeLists.txt @@ -143,10 +143,12 @@ if(CONFIG_TESTING_OSTEST) endif() endif() + if(CONFIG_ARCH_HAVE_VFORK) + list(APPEND SRCS vfork.c) + endif() + if(CONFIG_ARCH_HAVE_FORK) - if(CONFIG_SCHED_WAITPID) - list(APPEND SRCS vfork.c) - endif() + list(APPEND SRCS fork.c) endif() if(CONFIG_ARCH_SETJMP_H) diff --git a/testing/ostest/Makefile b/testing/ostest/Makefile index 4919a6204f9..c89b2466f4f 100644 --- a/testing/ostest/Makefile +++ b/testing/ostest/Makefile @@ -144,10 +144,14 @@ CSRCS += sigev_thread.c endif endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) -ifeq ($(CONFIG_SCHED_WAITPID),y) +# Each test is built where the primitive it tests exists. + +ifeq ($(CONFIG_ARCH_HAVE_VFORK),y) CSRCS += vfork.c endif + +ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +CSRCS += fork.c endif ifeq ($(CONFIG_ARCH_SETJMP_H),y) diff --git a/testing/ostest/fork.c b/testing/ostest/fork.c new file mode 100644 index 00000000000..9d2c3ae9137 --- /dev/null +++ b/testing/ostest/fork.c @@ -0,0 +1,276 @@ +/**************************************************************************** + * apps/testing/ostest/fork.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ostest.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FORK_HEAPSIZE 256 +#define FORK_PARENTMARK 0x5a +#define FORK_CHILDMARK 0xa5 + +/* Distinct values written through a pointer to a stack local, to check that + * the child's stack is at the same virtual address as the parent's. + */ + +#define FORK_STACKPARENT 0x1234 +#define FORK_STACKCHILD 0x5678 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* .data and .bss, which a fork() child must get its own copy of */ + +static volatile int g_forkdata = 1; +static volatile int g_forkbss; +static FAR unsigned char *g_forkheap; + +/* Pointer to a local in fork_test()'s frame, taken before fork(). In .data + * rather than on the stack: the compiler could rematerialise a local + * pointer from the current stack pointer, which would test nothing. + */ + +static FAR volatile int *g_forkstackptr; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fork_child + * + * Description: + * Everything here is forbidden to a vfork() child and permitted to a + * fork() child. Returns the child's exit status. + * + ****************************************************************************/ + +static int fork_child(void) +{ + FAR char *scratch; + + /* The parent must see none of this. */ + + g_forkdata = 2; + g_forkbss = 2; + memset(g_forkheap, FORK_CHILDMARK, FORK_HEAPSIZE); + + /* A vfork() child may not call these; a fork() child may. */ + + scratch = malloc(64); + if (scratch == NULL) + { + printf("fork_test: ERROR Child could not malloc()\n"); + return 1; + } + + strlcpy(scratch, "child", 64); + printf("fork_test: Child running independently (%s)\n", scratch); + free(scratch); + + /* Give the parent time to make its own writes, so that if the two shared + * memory we would see the parent's values below rather than our own. + */ + + usleep(200 * 1000); + + if (g_forkdata != 2 || g_forkbss != 2) + { + printf("fork_test: ERROR Child saw the parent's writes: " + "data=%d bss=%d\n", g_forkdata, g_forkbss); + return 1; + } + + if (g_forkheap[0] != FORK_CHILDMARK || + g_forkheap[FORK_HEAPSIZE - 1] != FORK_CHILDMARK) + { + printf("fork_test: ERROR Child saw the parent's heap writes\n"); + return 1; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fork_test + * + * Description: + * Verify the defining property of POSIX fork(): the child gets its own + * copy of the parent's memory, in both directions. That is the exact + * opposite of the sharing vfork() gives. Also verifies that none of + * vfork()'s restrictions apply. + * + ****************************************************************************/ + +int fork_test(void) +{ + pid_t pid; + int status = 0; + int ret = 0; + + /* volatile so it is really in memory and the compare is not folded. */ + + volatile int stackvar = FORK_STACKPARENT; + + printf("fork_test: Started\n"); + + /* Publish its address before forking. See g_forkstackptr. */ + + g_forkstackptr = &stackvar; + + g_forkdata = 1; + g_forkbss = 1; + + g_forkheap = malloc(FORK_HEAPSIZE); + if (g_forkheap == NULL) + { + printf("fork_test: ERROR Failed to allocate the heap probe\n"); + ASSERT(false); + return -1; + } + + memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); + + pid = fork(); + if (pid == 0) + { + /* The child's stack is at the parent's virtual addresses, so this + * pointer names the child's own live local. A relocated stack would + * write into the copy of the parent's instead. + */ + + *g_forkstackptr = FORK_STACKCHILD; + if (stackvar != FORK_STACKCHILD) + { + printf("fork_test: ERROR Child stack was relocated: wrote %d " + "through %p, local at %p reads %d\n", + FORK_STACKCHILD, g_forkstackptr, &stackvar, stackvar); + _exit(1); + } + + /* Returns from fork_child() and from this branch -- both illegal + * for a vfork() child. + */ + + _exit(fork_child()); + } + else if (pid < 0) + { + printf("fork_test: ERROR fork() failed: %d\n", errno); + free(g_forkheap); + ASSERT(false); + return -1; + } + + /* Parent runs concurrently: write now, check isolation afterwards. */ + + g_forkdata = 3; + g_forkbss = 3; + memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); + +#ifdef CONFIG_SCHED_WAITPID + /* Wait for the child to be done before comparing memory. waitpid() blocks + * on a child that is still alive whether or not its exit status will be + * retained, so this synchronises either way; ECHILD simply means the child + * had already finished, which is just as good. It is not a failure: + * ostest_main() sets SA_NOCLDWAIT on SIGCHLD for the whole run, so an + * exited child's status is not kept even where CONFIG_SCHED_CHILD_STATUS + * is enabled. + */ + + if (waitpid(pid, &status, 0) != pid && errno != ECHILD) + { + printf("fork_test: ERROR waitpid() failed: %d\n", errno); + free(g_forkheap); + ASSERT(false); + return -1; + } +#else + sleep(1); +#endif + + if (g_forkdata != 3 || g_forkbss != 3) + { + printf("fork_test: ERROR Parent saw the child's writes: " + "data=%d bss=%d (expected 3, 3)\n", g_forkdata, g_forkbss); + ret = -1; + } + + if (g_forkheap[0] != FORK_PARENTMARK || + g_forkheap[FORK_HEAPSIZE - 1] != FORK_PARENTMARK) + { + printf("fork_test: ERROR Parent saw the child's heap writes\n"); + ret = -1; + } + + /* The child wrote to the same stack address; the parent must not see it */ + + if (stackvar != FORK_STACKPARENT) + { + printf("fork_test: ERROR Parent saw the child's stack write: " + "%d (expected %d)\n", stackvar, FORK_STACKPARENT); + ret = -1; + } + +#ifdef CONFIG_SCHED_WAITPID + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + printf("fork_test: ERROR Child reported failure, status 0x%04x\n", + status); + ret = -1; + } +#endif + + free(g_forkheap); + g_forkheap = NULL; + + if (ret < 0) + { + ASSERT(false); + return ret; + } + + printf("fork_test: Parent and child had independent memory\n"); + return 0; +} diff --git a/testing/ostest/ostest.h b/testing/ostest/ostest.h index 2be54de5a68..8fa6d64d0a9 100644 --- a/testing/ostest/ostest.h +++ b/testing/ostest/ostest.h @@ -284,10 +284,16 @@ void sched_lock_test(void); /* vfork.c ******************************************************************/ -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) +#ifdef CONFIG_ARCH_HAVE_VFORK int vfork_test(void); #endif +/* fork.c *******************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_FORK +int fork_test(void); +#endif + /* setjmp.c *****************************************************************/ void setjmp_test(void); diff --git a/testing/ostest/ostest_main.c b/testing/ostest/ostest_main.c index 964771d907c..77ebe95acc7 100644 --- a/testing/ostest/ostest_main.c +++ b/testing/ostest/ostest_main.c @@ -224,6 +224,23 @@ static int user_main(int argc, char *argv[]) g_mmbefore = mallinfo(); g_mmprevious = g_mmbefore; + /* Run the two fork primitives first. They exercise the lowest-level + * machinery in the suite, so a fault takes the process down rather than + * reporting a failure -- better to learn that in seconds. + */ + +#ifdef CONFIG_ARCH_HAVE_VFORK + printf("\nuser_main: vfork() test\n"); + vfork_test(); + check_test_memory_usage(); +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK + printf("\nuser_main: fork() test\n"); + fork_test(); + check_test_memory_usage(); +#endif + printf("\nuser_main: Begin argument test\n"); printf("user_main: Started with argc=%d\n", argc); @@ -637,12 +654,6 @@ static int user_main(int argc, char *argv[]) check_test_memory_usage(); #endif -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) && \ - !defined(CONFIG_ARCH_SIM) - printf("\nuser_main: vfork() test\n"); - vfork_test(); -#endif - #if defined(CONFIG_SMP) && defined(CONFIG_BUILD_FLAT) printf("\nuser_main: smp call test\n"); smp_call_test(); diff --git a/testing/ostest/vfork.c b/testing/ostest/vfork.c index bd6cfabadb9..473b19222bd 100644 --- a/testing/ostest/vfork.c +++ b/testing/ostest/vfork.c @@ -28,39 +28,41 @@ #include #include -#include #include #include +#include #include #include "ostest.h" -#if defined(CONFIG_ARCH_HAVE_FORK) && defined(CONFIG_SCHED_WAITPID) - /**************************************************************************** - * Private Data + * Public Functions ****************************************************************************/ -static volatile bool g_vforkchild; - /**************************************************************************** - * Public Functions + * Name: vfork_test + * + * Description: + * Verify the defining property of vfork(): the parent is suspended until + * the child _exit()s or exec()s. The child does only what POSIX + * permits -- _exit(), not exit(), which would flush stdio in the parent's + * address space. Since the child may not write memory, the observable is + * its exit status: an unsuspended parent would reach waitpid() first. + * ****************************************************************************/ int vfork_test(void) { pid_t pid; - g_vforkchild = false; + printf("vfork_test: Started\n"); + pid = vfork(); if (pid == 0) { - /* There is not very much that the child is permitted to do. Perhaps - * it can just set g_vforkchild. - */ + /* The only thing a vfork() child may do is leave. */ - g_vforkchild = true; - exit(0); + _exit(42); } else if (pid < 0) { @@ -68,22 +70,44 @@ int vfork_test(void) ASSERT(false); return -1; } - else + + /* Reached only once the child has exited or exec'ed. */ + +#ifdef CONFIG_SCHED_WAITPID { - sleep(1); - if (g_vforkchild) + int status = 0; + pid_t ret; + + ret = waitpid(pid, &status, 0); + + /* Two answers are correct, and which one comes back is a property of + * the configuration: a retained status must be exit(42), and ECHILD + * is equally good evidence -- it says the child was already gone when + * we asked. ostest_main() sets SA_NOCLDWAIT for the whole run, so + * testing CONFIG_SCHED_CHILD_STATUS alone is not enough. + */ + + if (ret == pid) { - printf("vfork_test: Child %d ran successfully\n", pid); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) + { + printf("vfork_test: ERROR Child %d status 0x%04x, expected " + "exit(42)\n", pid, status); + ASSERT(false); + return -1; + } } - else + else if (ret >= 0 || errno != ECHILD) { - printf("vfork_test: ERROR Child %d did not run\n", pid); + printf("vfork_test: ERROR waitpid() returned %d (%d), expected " + "the child's status or ECHILD\n", ret, errno); ASSERT(false); return -1; } } +#endif + printf("vfork_test: Child %d ran and exited before the parent resumed\n", + pid); return 0; } - -#endif /* CONFIG_ARCH_HAVE_FORK && CONFIG_SCHED_WAITPID */