All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrew Morton <akpm@linux-foundation.org>
To: akpm@linux-foundation.org, arnd@arndb.de, bp@alien8.de,
	catalin.marinas@arm.com, cl@linux.com, dan.j.williams@intel.com,
	dave.hansen@linux.intel.com, david@redhat.com,
	elena.reshetova@intel.com, guro@fb.com, hagen@jauu.net,
	hpa@zytor.com, James.Bottomley@HansenPartnership.com,
	jejb@linux.ibm.com, kirill@shutemov.name, linux-mm@kvack.org,
	lkp@intel.com, luto@kernel.org, mark.rutland@arm.com,
	mingo@redhat.com, mm-commits@vger.kernel.org,
	mtk.manpages@gmail.com, palmer@dabbelt.com,
	palmerdabbelt@google.com, paul.walmsley@sifive.com,
	peterz@infradead.org, rick.p.edgecombe@intel.com,
	rppt@linux.ibm.com, shakeelb@google.com, shuah@kernel.org,
	tglx@linutronix.de, torvalds@linux-foundation.org,
	tycho@tycho.ws, viro@zeniv.linux.org.uk, will@kernel.org,
	willy@infradead.org
Subject: [patch 14/54] secretmem: test: add basic selftest for memfd_secret(2)
Date: Wed, 07 Jul 2021 18:08:15 -0700	[thread overview]
Message-ID: <20210708010815.GLLeyFgrp%akpm@linux-foundation.org> (raw)
In-Reply-To: <20210707175950.eceddb86c6c555555d4730e2@linux-foundation.org>

From: Mike Rapoport <rppt@linux.ibm.com>
Subject: secretmem: test: add basic selftest for memfd_secret(2)

The test verifies that file descriptor created with memfd_secret does not
allow read/write operations, that secret memory mappings respect
RLIMIT_MEMLOCK and that remote accesses with process_vm_read() and
ptrace() to the secret memory fail.

Link: https://lkml.kernel.org/r/20210518072034.31572-8-rppt@kernel.org
Signed-off-by: Mike Rapoport <rppt@linux.ibm.com>
Acked-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Christopher Lameter <cl@linux.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Elena Reshetova <elena.reshetova@intel.com>
Cc: Hagen Paul Pfeifer <hagen@jauu.net>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: "Kirill A. Shutemov" <kirill@shutemov.name>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Palmer Dabbelt <palmer@dabbelt.com>
Cc: Palmer Dabbelt <palmerdabbelt@google.com>
Cc: Paul Walmsley <paul.walmsley@sifive.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Roman Gushchin <guro@fb.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Tycho Andersen <tycho@tycho.ws>
Cc: Will Deacon <will@kernel.org>
Cc: kernel test robot <lkp@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 tools/testing/selftests/vm/.gitignore     |    1 
 tools/testing/selftests/vm/Makefile       |    3 
 tools/testing/selftests/vm/memfd_secret.c |  296 ++++++++++++++++++++
 tools/testing/selftests/vm/run_vmtests.sh |   17 +
 4 files changed, 316 insertions(+), 1 deletion(-)

--- a/tools/testing/selftests/vm/.gitignore~secretmem-test-add-basic-selftest-for-memfd_secret2
+++ a/tools/testing/selftests/vm/.gitignore
@@ -24,5 +24,6 @@ va_128TBswitch
 map_fixed_noreplace
 write_to_hugetlbfs
 hmm-tests
+memfd_secret
 local_config.*
 split_huge_page_test
--- a/tools/testing/selftests/vm/Makefile~secretmem-test-add-basic-selftest-for-memfd_secret2
+++ a/tools/testing/selftests/vm/Makefile
@@ -35,6 +35,7 @@ TEST_GEN_FILES += madv_populate
 TEST_GEN_FILES += map_fixed_noreplace
 TEST_GEN_FILES += map_hugetlb
 TEST_GEN_FILES += map_populate
+TEST_GEN_FILES += memfd_secret
 TEST_GEN_FILES += mlock-random-test
 TEST_GEN_FILES += mlock2-tests
 TEST_GEN_FILES += mremap_dontunmap
@@ -135,7 +136,7 @@ warn_32bit_failure:
 endif
 endif
 
-$(OUTPUT)/mlock-random-test: LDLIBS += -lcap
+$(OUTPUT)/mlock-random-test $(OUTPUT)/memfd_secret: LDLIBS += -lcap
 
 $(OUTPUT)/gup_test: ../../../../mm/gup_test.h
 
--- /dev/null
+++ a/tools/testing/selftests/vm/memfd_secret.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright IBM Corporation, 2021
+ *
+ * Author: Mike Rapoport <rppt@linux.ibm.com>
+ */
+
+#define _GNU_SOURCE
+#include <sys/uio.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/resource.h>
+#include <sys/capability.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "../kselftest.h"
+
+#define fail(fmt, ...) ksft_test_result_fail(fmt, ##__VA_ARGS__)
+#define pass(fmt, ...) ksft_test_result_pass(fmt, ##__VA_ARGS__)
+#define skip(fmt, ...) ksft_test_result_skip(fmt, ##__VA_ARGS__)
+
+#ifdef __NR_memfd_secret
+
+#define PATTERN	0x55
+
+static const int prot = PROT_READ | PROT_WRITE;
+static const int mode = MAP_SHARED;
+
+static unsigned long page_size;
+static unsigned long mlock_limit_cur;
+static unsigned long mlock_limit_max;
+
+static int memfd_secret(unsigned int flags)
+{
+	return syscall(__NR_memfd_secret, flags);
+}
+
+static void test_file_apis(int fd)
+{
+	char buf[64];
+
+	if ((read(fd, buf, sizeof(buf)) >= 0) ||
+	    (write(fd, buf, sizeof(buf)) >= 0) ||
+	    (pread(fd, buf, sizeof(buf), 0) >= 0) ||
+	    (pwrite(fd, buf, sizeof(buf), 0) >= 0))
+		fail("unexpected file IO\n");
+	else
+		pass("file IO is blocked as expected\n");
+}
+
+static void test_mlock_limit(int fd)
+{
+	size_t len;
+	char *mem;
+
+	len = mlock_limit_cur;
+	mem = mmap(NULL, len, prot, mode, fd, 0);
+	if (mem == MAP_FAILED) {
+		fail("unable to mmap secret memory\n");
+		return;
+	}
+	munmap(mem, len);
+
+	len = mlock_limit_max * 2;
+	mem = mmap(NULL, len, prot, mode, fd, 0);
+	if (mem != MAP_FAILED) {
+		fail("unexpected mlock limit violation\n");
+		munmap(mem, len);
+		return;
+	}
+
+	pass("mlock limit is respected\n");
+}
+
+static void try_process_vm_read(int fd, int pipefd[2])
+{
+	struct iovec liov, riov;
+	char buf[64];
+	char *mem;
+
+	if (read(pipefd[0], &mem, sizeof(mem)) < 0) {
+		fail("pipe write: %s\n", strerror(errno));
+		exit(KSFT_FAIL);
+	}
+
+	liov.iov_len = riov.iov_len = sizeof(buf);
+	liov.iov_base = buf;
+	riov.iov_base = mem;
+
+	if (process_vm_readv(getppid(), &liov, 1, &riov, 1, 0) < 0) {
+		if (errno == ENOSYS)
+			exit(KSFT_SKIP);
+		exit(KSFT_PASS);
+	}
+
+	exit(KSFT_FAIL);
+}
+
+static void try_ptrace(int fd, int pipefd[2])
+{
+	pid_t ppid = getppid();
+	int status;
+	char *mem;
+	long ret;
+
+	if (read(pipefd[0], &mem, sizeof(mem)) < 0) {
+		perror("pipe write");
+		exit(KSFT_FAIL);
+	}
+
+	ret = ptrace(PTRACE_ATTACH, ppid, 0, 0);
+	if (ret) {
+		perror("ptrace_attach");
+		exit(KSFT_FAIL);
+	}
+
+	ret = waitpid(ppid, &status, WUNTRACED);
+	if ((ret != ppid) || !(WIFSTOPPED(status))) {
+		fprintf(stderr, "weird waitppid result %ld stat %x\n",
+			ret, status);
+		exit(KSFT_FAIL);
+	}
+
+	if (ptrace(PTRACE_PEEKDATA, ppid, mem, 0))
+		exit(KSFT_PASS);
+
+	exit(KSFT_FAIL);
+}
+
+static void check_child_status(pid_t pid, const char *name)
+{
+	int status;
+
+	waitpid(pid, &status, 0);
+
+	if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP) {
+		skip("%s is not supported\n", name);
+		return;
+	}
+
+	if ((WIFEXITED(status) && WEXITSTATUS(status) == KSFT_PASS) ||
+	    WIFSIGNALED(status)) {
+		pass("%s is blocked as expected\n", name);
+		return;
+	}
+
+	fail("%s: unexpected memory access\n", name);
+}
+
+static void test_remote_access(int fd, const char *name,
+			       void (*func)(int fd, int pipefd[2]))
+{
+	int pipefd[2];
+	pid_t pid;
+	char *mem;
+
+	if (pipe(pipefd)) {
+		fail("pipe failed: %s\n", strerror(errno));
+		return;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		fail("fork failed: %s\n", strerror(errno));
+		return;
+	}
+
+	if (pid == 0) {
+		func(fd, pipefd);
+		return;
+	}
+
+	mem = mmap(NULL, page_size, prot, mode, fd, 0);
+	if (mem == MAP_FAILED) {
+		fail("Unable to mmap secret memory\n");
+		return;
+	}
+
+	ftruncate(fd, page_size);
+	memset(mem, PATTERN, page_size);
+
+	if (write(pipefd[1], &mem, sizeof(mem)) < 0) {
+		fail("pipe write: %s\n", strerror(errno));
+		return;
+	}
+
+	check_child_status(pid, name);
+}
+
+static void test_process_vm_read(int fd)
+{
+	test_remote_access(fd, "process_vm_read", try_process_vm_read);
+}
+
+static void test_ptrace(int fd)
+{
+	test_remote_access(fd, "ptrace", try_ptrace);
+}
+
+static int set_cap_limits(rlim_t max)
+{
+	struct rlimit new;
+	cap_t cap = cap_init();
+
+	new.rlim_cur = max;
+	new.rlim_max = max;
+	if (setrlimit(RLIMIT_MEMLOCK, &new)) {
+		perror("setrlimit() returns error");
+		return -1;
+	}
+
+	/* drop capabilities including CAP_IPC_LOCK */
+	if (cap_set_proc(cap)) {
+		perror("cap_set_proc() returns error");
+		return -2;
+	}
+
+	return 0;
+}
+
+static void prepare(void)
+{
+	struct rlimit rlim;
+
+	page_size = sysconf(_SC_PAGE_SIZE);
+	if (!page_size)
+		ksft_exit_fail_msg("Failed to get page size %s\n",
+				   strerror(errno));
+
+	if (getrlimit(RLIMIT_MEMLOCK, &rlim))
+		ksft_exit_fail_msg("Unable to detect mlock limit: %s\n",
+				   strerror(errno));
+
+	mlock_limit_cur = rlim.rlim_cur;
+	mlock_limit_max = rlim.rlim_max;
+
+	printf("page_size: %ld, mlock.soft: %ld, mlock.hard: %ld\n",
+	       page_size, mlock_limit_cur, mlock_limit_max);
+
+	if (page_size > mlock_limit_cur)
+		mlock_limit_cur = page_size;
+	if (page_size > mlock_limit_max)
+		mlock_limit_max = page_size;
+
+	if (set_cap_limits(mlock_limit_max))
+		ksft_exit_fail_msg("Unable to set mlock limit: %s\n",
+				   strerror(errno));
+}
+
+#define NUM_TESTS 4
+
+int main(int argc, char *argv[])
+{
+	int fd;
+
+	prepare();
+
+	ksft_print_header();
+	ksft_set_plan(NUM_TESTS);
+
+	fd = memfd_secret(0);
+	if (fd < 0) {
+		if (errno == ENOSYS)
+			ksft_exit_skip("memfd_secret is not supported\n");
+		else
+			ksft_exit_fail_msg("memfd_secret failed: %s\n",
+					   strerror(errno));
+	}
+
+	test_mlock_limit(fd);
+	test_file_apis(fd);
+	test_process_vm_read(fd);
+	test_ptrace(fd);
+
+	close(fd);
+
+	ksft_exit(!ksft_get_fail_cnt());
+}
+
+#else /* __NR_memfd_secret */
+
+int main(int argc, char *argv[])
+{
+	printf("skip: skipping memfd_secret test (missing __NR_memfd_secret)\n");
+	return KSFT_SKIP;
+}
+
+#endif /* __NR_memfd_secret */
--- a/tools/testing/selftests/vm/run_vmtests.sh~secretmem-test-add-basic-selftest-for-memfd_secret2
+++ a/tools/testing/selftests/vm/run_vmtests.sh
@@ -362,4 +362,21 @@ else
 	exitcode=1
 fi
 
+echo "running memfd_secret test"
+echo "------------------------------------"
+./memfd_secret
+ret_val=$?
+
+if [ $ret_val -eq 0 ]; then
+	echo "[PASS]"
+elif [ $ret_val -eq $ksft_skip ]; then
+	echo "[SKIP]"
+	exitcode=$ksft_skip
+else
+	echo "[FAIL]"
+	exitcode=1
+fi
+
+exit $exitcode
+
 exit $exitcode
_

  parent reply	other threads:[~2021-07-08  1:08 UTC|newest]

Thread overview: 75+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-08  0:59 incoming Andrew Morton
2021-07-08  1:07 ` [patch 01/54] lib/test: fix spelling mistakes Andrew Morton
2021-07-08  1:07 ` [patch 02/54] lib: " Andrew Morton
2021-07-08  1:07 ` [patch 03/54] lib: fix spelling mistakes in header files Andrew Morton
2021-07-08  1:07 ` [patch 04/54] hexagon: handle {,SOFT}IRQENTRY_TEXT in linker script Andrew Morton
2021-07-08  1:07 ` [patch 05/54] hexagon: use common DISCARDS macro Andrew Morton
2021-07-08  1:07 ` [patch 06/54] hexagon: select ARCH_WANT_LD_ORPHAN_WARN Andrew Morton
2021-07-08  1:07 ` [patch 07/54] mm/slub: use stackdepot to save stack trace in objects Andrew Morton
2021-07-16  7:39   ` Christoph Hellwig
2021-07-16  8:57     ` Vlastimil Babka
2021-07-16  9:12       ` Christoph Hellwig
2021-07-16 20:12     ` Linus Torvalds
2021-07-16 20:12       ` Linus Torvalds
2021-07-16 22:37       ` Vlastimil Babka
2021-07-17 17:34         ` Randy Dunlap
2021-07-18  7:29           ` Vlastimil Babka
2021-07-18 14:17             ` Randy Dunlap
2021-07-08  1:07 ` [patch 08/54] mmap: make mlock_future_check() global Andrew Morton
2021-07-08  1:07 ` [patch 09/54] riscv/Kconfig: make direct map manipulation options depend on MMU Andrew Morton
2021-07-08  1:07 ` [patch 10/54] set_memory: allow querying whether set_direct_map_*() is actually enabled Andrew Morton
2021-07-08  1:08 ` [patch 11/54] mm: introduce memfd_secret system call to create "secret" memory areas Andrew Morton
2021-07-08  3:13   ` Linus Torvalds
2021-07-08  3:13     ` Linus Torvalds
2021-07-08  5:21     ` Mike Rapoport
2021-07-08 18:38       ` Linus Torvalds
2021-07-08 18:38         ` Linus Torvalds
2021-07-08 20:13         ` Hagen Paul Pfeifer
2021-07-09 15:44           ` Mike Rapoport
2021-07-08  1:08 ` [patch 12/54] PM: hibernate: disable when there are active secretmem users Andrew Morton
2021-07-08  3:15   ` Linus Torvalds
2021-07-08  3:15     ` Linus Torvalds
2021-07-08  5:30     ` Mike Rapoport
2021-07-08  1:08 ` [patch 13/54] arch, mm: wire up memfd_secret system call where relevant Andrew Morton
2021-07-08  1:08 ` Andrew Morton [this message]
2021-07-08  1:08 ` [patch 15/54] mm: fix spelling mistakes in header files Andrew Morton
2021-07-08  1:08 ` [patch 16/54] mm: add setup_initial_init_mm() helper Andrew Morton
2021-07-08  1:08 ` [patch 17/54] arc: convert to setup_initial_init_mm() Andrew Morton
2021-07-08  1:08 ` [patch 18/54] arm: " Andrew Morton
2021-07-08  1:08 ` [patch 19/54] arm64: " Andrew Morton
2021-07-08  1:08 ` [patch 20/54] csky: " Andrew Morton
2021-07-08  1:08 ` [patch 21/54] h8300: " Andrew Morton
2021-07-08  1:08 ` [patch 22/54] m68k: " Andrew Morton
2021-07-08  1:08 ` [patch 23/54] nds32: " Andrew Morton
2021-07-08  1:08 ` [patch 24/54] nios2: " Andrew Morton
2021-07-08  1:08 ` [patch 25/54] openrisc: " Andrew Morton
2021-07-08  1:08 ` [patch 26/54] powerpc: " Andrew Morton
2021-07-08  4:46   ` Christophe Leroy
2021-07-08  1:08 ` [patch 27/54] riscv: " Andrew Morton
2021-07-08  1:08 ` [patch 28/54] s390: " Andrew Morton
2021-07-08  1:09 ` [patch 29/54] sh: " Andrew Morton
2021-07-08  1:09 ` [patch 30/54] x86: " Andrew Morton
2021-07-08  1:09 ` [patch 31/54] buildid: only consider GNU notes for build ID parsing Andrew Morton
2021-07-08  1:09 ` [patch 32/54] buildid: add API to parse build ID out of buffer Andrew Morton
2021-07-08  1:09 ` [patch 33/54] buildid: stash away kernels build ID on init Andrew Morton
2021-07-08  1:09 ` [patch 34/54] dump_stack: add vmlinux build ID to stack traces Andrew Morton
2021-07-08  1:09 ` [patch 35/54] module: add printk formats to add module build ID to stacktraces Andrew Morton
2021-07-08  1:09 ` [patch 36/54] arm64: stacktrace: use %pSb for backtrace printing Andrew Morton
2021-07-08  1:09 ` [patch 37/54] x86/dumpstack: use %pSb/%pBb " Andrew Morton
2021-07-08  1:09 ` [patch 38/54] scripts/decode_stacktrace.sh: support debuginfod Andrew Morton
2021-07-08  1:09 ` [patch 39/54] scripts/decode_stacktrace.sh: silence stderr messages from addr2line/nm Andrew Morton
2021-07-08  1:09 ` [patch 40/54] scripts/decode_stacktrace.sh: indicate 'auto' can be used for base path Andrew Morton
2021-07-08  1:09 ` [patch 41/54] buildid: mark some arguments const Andrew Morton
2021-07-08  1:09 ` [patch 42/54] buildid: fix kernel-doc notation Andrew Morton
2021-07-08  1:09 ` [patch 43/54] kdump: use vmlinux_build_id to simplify Andrew Morton
2021-07-08  1:09 ` [patch 44/54] mm: rename pud_page_vaddr to pud_pgtable and make it return pmd_t * Andrew Morton
2021-07-08  1:09 ` [patch 45/54] mm: rename p4d_page_vaddr to p4d_pgtable and make it return pud_t * Andrew Morton
2021-07-08  1:09 ` [patch 46/54] selftest/mremap_test: update the test to handle pagesize other than 4K Andrew Morton
2021-07-08  1:10 ` [patch 47/54] selftest/mremap_test: avoid crash with static build Andrew Morton
2021-07-08  1:10 ` [patch 48/54] mm/mremap: convert huge PUD move to separate helper Andrew Morton
2021-07-08  1:10 ` [patch 49/54] mm/mremap: don't enable optimized PUD move if page table levels is 2 Andrew Morton
2021-07-08  1:10 ` [patch 50/54] mm/mremap: use pmd/pud_poplulate to update page table entries Andrew Morton
2021-07-08  1:10 ` [patch 51/54] mm/mremap: hold the rmap lock in write mode when moving " Andrew Morton
2021-07-08  1:10 ` [patch 52/54] mm/mremap: allow arch runtime override Andrew Morton
2021-07-08  1:10 ` [patch 53/54] powerpc/book3s64/mm: update flush_tlb_range to flush page walk cache Andrew Morton
2021-07-08  1:10 ` [patch 54/54] powerpc/mm: enable HAVE_MOVE_PMD support Andrew Morton

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=20210708010815.GLLeyFgrp%akpm@linux-foundation.org \
    --to=akpm@linux-foundation.org \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=arnd@arndb.de \
    --cc=bp@alien8.de \
    --cc=catalin.marinas@arm.com \
    --cc=cl@linux.com \
    --cc=dan.j.williams@intel.com \
    --cc=dave.hansen@linux.intel.com \
    --cc=david@redhat.com \
    --cc=elena.reshetova@intel.com \
    --cc=guro@fb.com \
    --cc=hagen@jauu.net \
    --cc=hpa@zytor.com \
    --cc=jejb@linux.ibm.com \
    --cc=kirill@shutemov.name \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=lkp@intel.com \
    --cc=luto@kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mingo@redhat.com \
    --cc=mm-commits@vger.kernel.org \
    --cc=mtk.manpages@gmail.com \
    --cc=palmer@dabbelt.com \
    --cc=palmerdabbelt@google.com \
    --cc=paul.walmsley@sifive.com \
    --cc=peterz@infradead.org \
    --cc=rick.p.edgecombe@intel.com \
    --cc=rppt@linux.ibm.com \
    --cc=shakeelb@google.com \
    --cc=shuah@kernel.org \
    --cc=tglx@linutronix.de \
    --cc=torvalds@linux-foundation.org \
    --cc=tycho@tycho.ws \
    --cc=viro@zeniv.linux.org.uk \
    --cc=will@kernel.org \
    --cc=willy@infradead.org \
    /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.