From: "Iván Ezequiel Rodriguez" <ivanrwcm25@gmail.com>
To: Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Paul Walmsley <pjw@kernel.org>,
Palmer Dabbelt <palmer@dabbelt.com>,
linux-arm-kernel@lists.infradead.org,
linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Mark Rutland <mark.rutland@arm.com>,
Mark Brown <broonie@kernel.org>,
Deepak Gupta <debug@rivosinc.com>,
Alexandre Ghiti <alex@ghiti.fr>,
Albert Ou <aou@eecs.berkeley.edu>, Shuah Khan <shuah@kernel.org>,
Christian Brauner <brauner@kernel.org>,
James Morse <james.morse@arm.com>,
Yury Khrustalev <yury.khrustalev@arm.com>,
Thiago Jung Bauermann <thiago.bauermann@linaro.org>,
Samuel Holland <samuel.holland@sifive.com>,
Zong Li <zong.li@sifive.com>,
Charlie Jenkins <thecharlesjenkins@gmail.com>,
Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>,
Zishun Yi <vulab@iscas.ac.cn>,
"Peter Zijlstra (Intel)" <peterz@infradead.org>
Subject: [PATCH v3 4/4] selftests/riscv: shadow stack must not leak on a failed clone3
Date: Wed, 28 Oct 2026 12:43:15 -0300 [thread overview]
Message-ID: <20261028154315.1272705-5-ivanrwcm25@gmail.com> (raw)
In-Reply-To: <20261028154315.1272705-1-ivanrwcm25@gmail.com>
Same clone3 bad-pidfd check as the arm64 GCS selftest.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
2 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile
index 418b4b5325a5..dbecb1fd3b82 100644
--- a/tools/testing/selftests/riscv/cfi/Makefile
+++ b/tools/testing/selftests/riscv/cfi/Makefile
@@ -13,10 +13,13 @@ endif
ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0)
-TEST_GEN_PROGS := cfitests
+TEST_GEN_PROGS := cfitests shstk-clone-pidfd
$(OUTPUT)/cfitests: cfitests.c shadowstack.c
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/shstk-clone-pidfd: shstk-clone-pidfd.c
+ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
else
$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2)
diff --git a/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
new file mode 100644
index 000000000000..1f16d9c513e6
--- /dev/null
+++ b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must
+ * not leak a shadow-stack VMA. copy_thread() maps one, then put_user()
+ * fails and copy_process() calls exit_thread(). Without HAVE_EXIT_THREAD
+ * that free is a no-op and the VMA stays in the parent's mm.
+ *
+ * After enabling the shadow stack, do not return from main(): libc
+ * frames above that point have no shadow-stack entries.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest.h"
+#include "cfi_rv_test.h"
+
+#ifndef __NR_clone3
+#define __NR_clone3 435
+#endif
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#endif
+
+#define NCLONE 16
+#define STACK_SIZE (64 * 1024)
+
+static int count_ss_vmas(void)
+{
+ FILE *f;
+ char line[256];
+ int vmas = 0;
+
+ f = fopen("/proc/self/smaps", "r");
+ if (!f)
+ ksft_exit_fail_msg("fopen /proc/self/smaps: %s\n",
+ strerror(errno));
+
+ while (fgets(line, sizeof(line), f)) {
+ if (!strncmp(line, "VmFlags:", 8) && strstr(line, " ss"))
+ vmas++;
+ }
+ fclose(f);
+ return vmas;
+}
+
+/*
+ * Raw ecall: the child resumes on an empty shadow stack, so a libc
+ * return after clone3 would fault.
+ */
+static long clone_pidfd(void *stack, size_t stack_size, int *pidfd_ptr)
+{
+ struct clone_args args = {
+ .flags = CLONE_VM | CLONE_PIDFD,
+ .pidfd = (uint64_t)(uintptr_t)pidfd_ptr,
+ .exit_signal = SIGCHLD,
+ .stack = (uint64_t)(uintptr_t)stack,
+ .stack_size = stack_size,
+ };
+ register long num __asm__("a7") = __NR_clone3;
+ register long ret __asm__("a0") = (long)&args;
+ register long sz __asm__("a1") = sizeof(args);
+ int st;
+
+ asm volatile("ecall"
+ : "+r"(ret)
+ : "r"(num), "r"(sz)
+ : "memory", "cc");
+ if (ret == 0) {
+ register long n __asm__("a7") = __NR_exit;
+ register long e __asm__("a0") = 0;
+
+ asm volatile("ecall" : : "r"(n), "r"(e) : "memory");
+ __builtin_unreachable();
+ }
+ if (ret < 0)
+ return ret;
+ if (waitpid((pid_t)ret, &st, 0) < 0)
+ return -errno;
+ if (!WIFEXITED(st) || WEXITSTATUS(st) != 0)
+ return 1;
+ return 0;
+}
+
+int main(void)
+{
+ void *stack;
+ void *bad_pidfd;
+ unsigned long ss_mode = 0;
+ long rc;
+ int pidfd = -1;
+ int vmas0, vmas1;
+ int i, n_efault = 0;
+
+ ksft_print_header();
+
+ stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED)
+ ksft_exit_fail_msg("mmap stack: %s\n", strerror(errno));
+
+ bad_pidfd = mmap(NULL, 4096, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (bad_pidfd == MAP_FAILED)
+ ksft_exit_fail_msg("mmap pidfd page: %s\n", strerror(errno));
+
+ rc = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_mode,
+ 0, 0, 0);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EOPNOTSUPP)
+ ksft_exit_skip("shadow stack not available: %ld\n", rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc);
+
+ if (!(ss_mode & PR_SHADOW_STACK_ENABLE)) {
+ rc = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+ PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+ if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY)
+ ksft_exit_skip("cannot enable shadow stack: %ld\n",
+ rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_SET_SHADOW_STACK_STATUS: %ld\n",
+ rc);
+ }
+
+ rc = clone_pidfd(stack, STACK_SIZE, &pidfd);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EPERM)
+ ksft_exit_skip("clone3 valid pidfd unavailable: %s\n",
+ strerror(-rc));
+
+ ksft_set_plan(3);
+
+ ksft_test_result(rc == 0 && pidfd >= 0,
+ "clone3 valid pidfd (rc=%ld pidfd=%d)\n",
+ rc, pidfd);
+ if (pidfd >= 0)
+ close(pidfd);
+
+ vmas0 = count_ss_vmas();
+
+ for (i = 0; i < NCLONE; i++) {
+ rc = clone_pidfd(stack, STACK_SIZE, bad_pidfd);
+ if (rc == -EFAULT)
+ n_efault++;
+ }
+ ksft_test_result(n_efault == NCLONE,
+ "clone3 bad pidfd returns -EFAULT x%d\n", NCLONE);
+
+ vmas1 = count_ss_vmas();
+ ksft_test_result(vmas1 == vmas0,
+ "no shadow-stack VMA leak (%d -> %d)\n",
+ vmas0, vmas1);
+
+ ksft_finished();
+}
--
2.43.0
WARNING: multiple messages have this Message-ID (diff)
From: "Iván Ezequiel Rodriguez" <ivanrwcm25@gmail.com>
To: Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Paul Walmsley <pjw@kernel.org>,
Palmer Dabbelt <palmer@dabbelt.com>,
linux-arm-kernel@lists.infradead.org,
linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Mark Rutland <mark.rutland@arm.com>,
Mark Brown <broonie@kernel.org>,
Deepak Gupta <debug@rivosinc.com>,
Alexandre Ghiti <alex@ghiti.fr>,
Albert Ou <aou@eecs.berkeley.edu>, Shuah Khan <shuah@kernel.org>,
Christian Brauner <brauner@kernel.org>,
James Morse <james.morse@arm.com>,
Yury Khrustalev <yury.khrustalev@arm.com>,
Thiago Jung Bauermann <thiago.bauermann@linaro.org>,
Samuel Holland <samuel.holland@sifive.com>,
Zong Li <zong.li@sifive.com>,
Charlie Jenkins <thecharlesjenkins@gmail.com>,
Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>,
Zishun Yi <vulab@iscas.ac.cn>,
"Peter Zijlstra (Intel)" <peterz@infradead.org>
Subject: [PATCH v3 4/4] selftests/riscv: shadow stack must not leak on a failed clone3
Date: Wed, 28 Oct 2026 12:43:15 -0300 [thread overview]
Message-ID: <20261028154315.1272705-5-ivanrwcm25@gmail.com> (raw)
In-Reply-To: <20261028154315.1272705-1-ivanrwcm25@gmail.com>
Same clone3 bad-pidfd check as the arm64 GCS selftest.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
2 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile
index 418b4b5325a5..dbecb1fd3b82 100644
--- a/tools/testing/selftests/riscv/cfi/Makefile
+++ b/tools/testing/selftests/riscv/cfi/Makefile
@@ -13,10 +13,13 @@ endif
ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0)
-TEST_GEN_PROGS := cfitests
+TEST_GEN_PROGS := cfitests shstk-clone-pidfd
$(OUTPUT)/cfitests: cfitests.c shadowstack.c
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/shstk-clone-pidfd: shstk-clone-pidfd.c
+ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
else
$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2)
diff --git a/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
new file mode 100644
index 000000000000..1f16d9c513e6
--- /dev/null
+++ b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must
+ * not leak a shadow-stack VMA. copy_thread() maps one, then put_user()
+ * fails and copy_process() calls exit_thread(). Without HAVE_EXIT_THREAD
+ * that free is a no-op and the VMA stays in the parent's mm.
+ *
+ * After enabling the shadow stack, do not return from main(): libc
+ * frames above that point have no shadow-stack entries.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest.h"
+#include "cfi_rv_test.h"
+
+#ifndef __NR_clone3
+#define __NR_clone3 435
+#endif
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#endif
+
+#define NCLONE 16
+#define STACK_SIZE (64 * 1024)
+
+static int count_ss_vmas(void)
+{
+ FILE *f;
+ char line[256];
+ int vmas = 0;
+
+ f = fopen("/proc/self/smaps", "r");
+ if (!f)
+ ksft_exit_fail_msg("fopen /proc/self/smaps: %s\n",
+ strerror(errno));
+
+ while (fgets(line, sizeof(line), f)) {
+ if (!strncmp(line, "VmFlags:", 8) && strstr(line, " ss"))
+ vmas++;
+ }
+ fclose(f);
+ return vmas;
+}
+
+/*
+ * Raw ecall: the child resumes on an empty shadow stack, so a libc
+ * return after clone3 would fault.
+ */
+static long clone_pidfd(void *stack, size_t stack_size, int *pidfd_ptr)
+{
+ struct clone_args args = {
+ .flags = CLONE_VM | CLONE_PIDFD,
+ .pidfd = (uint64_t)(uintptr_t)pidfd_ptr,
+ .exit_signal = SIGCHLD,
+ .stack = (uint64_t)(uintptr_t)stack,
+ .stack_size = stack_size,
+ };
+ register long num __asm__("a7") = __NR_clone3;
+ register long ret __asm__("a0") = (long)&args;
+ register long sz __asm__("a1") = sizeof(args);
+ int st;
+
+ asm volatile("ecall"
+ : "+r"(ret)
+ : "r"(num), "r"(sz)
+ : "memory", "cc");
+ if (ret == 0) {
+ register long n __asm__("a7") = __NR_exit;
+ register long e __asm__("a0") = 0;
+
+ asm volatile("ecall" : : "r"(n), "r"(e) : "memory");
+ __builtin_unreachable();
+ }
+ if (ret < 0)
+ return ret;
+ if (waitpid((pid_t)ret, &st, 0) < 0)
+ return -errno;
+ if (!WIFEXITED(st) || WEXITSTATUS(st) != 0)
+ return 1;
+ return 0;
+}
+
+int main(void)
+{
+ void *stack;
+ void *bad_pidfd;
+ unsigned long ss_mode = 0;
+ long rc;
+ int pidfd = -1;
+ int vmas0, vmas1;
+ int i, n_efault = 0;
+
+ ksft_print_header();
+
+ stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED)
+ ksft_exit_fail_msg("mmap stack: %s\n", strerror(errno));
+
+ bad_pidfd = mmap(NULL, 4096, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (bad_pidfd == MAP_FAILED)
+ ksft_exit_fail_msg("mmap pidfd page: %s\n", strerror(errno));
+
+ rc = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_mode,
+ 0, 0, 0);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EOPNOTSUPP)
+ ksft_exit_skip("shadow stack not available: %ld\n", rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc);
+
+ if (!(ss_mode & PR_SHADOW_STACK_ENABLE)) {
+ rc = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+ PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+ if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY)
+ ksft_exit_skip("cannot enable shadow stack: %ld\n",
+ rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_SET_SHADOW_STACK_STATUS: %ld\n",
+ rc);
+ }
+
+ rc = clone_pidfd(stack, STACK_SIZE, &pidfd);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EPERM)
+ ksft_exit_skip("clone3 valid pidfd unavailable: %s\n",
+ strerror(-rc));
+
+ ksft_set_plan(3);
+
+ ksft_test_result(rc == 0 && pidfd >= 0,
+ "clone3 valid pidfd (rc=%ld pidfd=%d)\n",
+ rc, pidfd);
+ if (pidfd >= 0)
+ close(pidfd);
+
+ vmas0 = count_ss_vmas();
+
+ for (i = 0; i < NCLONE; i++) {
+ rc = clone_pidfd(stack, STACK_SIZE, bad_pidfd);
+ if (rc == -EFAULT)
+ n_efault++;
+ }
+ ksft_test_result(n_efault == NCLONE,
+ "clone3 bad pidfd returns -EFAULT x%d\n", NCLONE);
+
+ vmas1 = count_ss_vmas();
+ ksft_test_result(vmas1 == vmas0,
+ "no shadow-stack VMA leak (%d -> %d)\n",
+ vmas0, vmas1);
+
+ ksft_finished();
+}
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
next prev parent reply other threads:[~2026-10-28 15:44 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 15:43 [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 15:43 ` [PATCH v3 1/4] arm64/gcs: release GCS " Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 18:41 ` Mark Brown
2026-10-28 18:41 ` Mark Brown
2026-10-29 20:32 ` Catalin Marinas
2026-10-29 20:32 ` Catalin Marinas
2026-10-29 22:36 ` Mark Brown
2026-10-29 22:36 ` Mark Brown
2026-10-28 15:43 ` [PATCH v3 2/4] riscv/shstk: release shadow stack " Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 15:43 ` [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3 Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 18:31 ` Mark Brown
2026-10-28 18:31 ` Mark Brown
2026-10-28 15:43 ` Iván Ezequiel Rodriguez [this message]
2026-10-28 15:43 ` [PATCH v3 4/4] selftests/riscv: shadow stack " Iván Ezequiel Rodriguez
2026-10-29 20:57 ` (subset) [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork Catalin Marinas
2026-10-29 20:57 ` Catalin Marinas
2026-10-30 17:00 ` [PATCH 0/1] selftests/arm64: GCS must survive io_uring SQPOLL worker exit Iván Ezequiel Rodriguez
2026-10-30 17:00 ` [PATCH 1/1] " Iván Ezequiel Rodriguez
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261028154315.1272705-5-ivanrwcm25@gmail.com \
--to=ivanrwcm25@gmail.com \
--cc=alex@ghiti.fr \
--cc=aou@eecs.berkeley.edu \
--cc=brauner@kernel.org \
--cc=broonie@kernel.org \
--cc=catalin.marinas@arm.com \
--cc=debug@rivosinc.com \
--cc=james.morse@arm.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=mark.rutland@arm.com \
--cc=palmer@dabbelt.com \
--cc=peterz@infradead.org \
--cc=pjw@kernel.org \
--cc=samuel.holland@sifive.com \
--cc=shuah@kernel.org \
--cc=tan.shaopeng@jp.fujitsu.com \
--cc=thecharlesjenkins@gmail.com \
--cc=thiago.bauermann@linaro.org \
--cc=vulab@iscas.ac.cn \
--cc=will@kernel.org \
--cc=yury.khrustalev@arm.com \
--cc=zong.li@sifive.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.