From: Sasha Levin <sashal@kernel.org>
To: linux-kernel@vger.kernel.org, stable@vger.kernel.org
Cc: Rodrigo Campos <rodrigo@kinvolk.io>,
Sargun Dhillon <sargun@sargun.me>,
Tycho Andersen <tycho@tycho.pizza>,
Christian Brauner <christian.brauner@ubuntu.com>,
Kees Cook <keescook@chromium.org>,
Sasha Levin <sashal@kernel.org>,
linux-doc@vger.kernel.org
Subject: [PATCH AUTOSEL 5.13 85/85] seccomp: Support atomic "addfd + send reply"
Date: Sun, 4 Jul 2021 19:04:20 -0400 [thread overview]
Message-ID: <20210704230420.1488358-85-sashal@kernel.org> (raw)
In-Reply-To: <20210704230420.1488358-1-sashal@kernel.org>
From: Rodrigo Campos <rodrigo@kinvolk.io>
[ Upstream commit 0ae71c7720e3ae3aabd2e8a072d27f7bd173d25c ]
Alban Crequy reported a race condition userspace faces when we want to
add some fds and make the syscall return them[1] using seccomp notify.
The problem is that currently two different ioctl() calls are needed by
the process handling the syscalls (agent) for another userspace process
(target): SECCOMP_IOCTL_NOTIF_ADDFD to allocate the fd and
SECCOMP_IOCTL_NOTIF_SEND to return that value. Therefore, it is possible
for the agent to do the first ioctl to add a file descriptor but the
target is interrupted (EINTR) before the agent does the second ioctl()
call.
This patch adds a flag to the ADDFD ioctl() so it adds the fd and
returns that value atomically to the target program, as suggested by
Kees Cook[2]. This is done by simply allowing
seccomp_do_user_notification() to add the fd and return it in this case.
Therefore, in this case the target wakes up from the wait in
seccomp_do_user_notification() either to interrupt the syscall or to add
the fd and return it.
This "allocate an fd and return" functionality is useful for syscalls
that return a file descriptor only, like connect(2). Other syscalls that
return a file descriptor but not as return value (or return more than
one fd), like socketpair(), pipe(), recvmsg with SCM_RIGHTs, will not
work with this flag.
This effectively combines SECCOMP_IOCTL_NOTIF_ADDFD and
SECCOMP_IOCTL_NOTIF_SEND into an atomic opteration. The notification's
return value, nor error can be set by the user. Upon successful invocation
of the SECCOMP_IOCTL_NOTIF_ADDFD ioctl with the SECCOMP_ADDFD_FLAG_SEND
flag, the notifying process's errno will be 0, and the return value will
be the file descriptor number that was installed.
[1]: https://lore.kernel.org/lkml/CADZs7q4sw71iNHmV8EOOXhUKJMORPzF7thraxZYddTZsxta-KQ@mail.gmail.com/
[2]: https://lore.kernel.org/lkml/202012011322.26DCBC64F2@keescook/
Signed-off-by: Rodrigo Campos <rodrigo@kinvolk.io>
Signed-off-by: Sargun Dhillon <sargun@sargun.me>
Acked-by: Tycho Andersen <tycho@tycho.pizza>
Acked-by: Christian Brauner <christian.brauner@ubuntu.com>
Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20210517193908.3113-4-sargun@sargun.me
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
.../userspace-api/seccomp_filter.rst | 12 +++++
include/uapi/linux/seccomp.h | 1 +
kernel/seccomp.c | 51 ++++++++++++++++---
3 files changed, 58 insertions(+), 6 deletions(-)
diff --git a/Documentation/userspace-api/seccomp_filter.rst b/Documentation/userspace-api/seccomp_filter.rst
index 6efb41cc8072..d61219889e49 100644
--- a/Documentation/userspace-api/seccomp_filter.rst
+++ b/Documentation/userspace-api/seccomp_filter.rst
@@ -259,6 +259,18 @@ and ``ioctl(SECCOMP_IOCTL_NOTIF_SEND)`` a response, indicating what should be
returned to userspace. The ``id`` member of ``struct seccomp_notif_resp`` should
be the same ``id`` as in ``struct seccomp_notif``.
+Userspace can also add file descriptors to the notifying process via
+``ioctl(SECCOMP_IOCTL_NOTIF_ADDFD)``. The ``id`` member of
+``struct seccomp_notif_addfd`` should be the same ``id`` as in
+``struct seccomp_notif``. The ``newfd_flags`` flag may be used to set flags
+like O_EXEC on the file descriptor in the notifying process. If the supervisor
+wants to inject the file descriptor with a specific number, the
+``SECCOMP_ADDFD_FLAG_SETFD`` flag can be used, and set the ``newfd`` member to
+the specific number to use. If that file descriptor is already open in the
+notifying process it will be replaced. The supervisor can also add an FD, and
+respond atomically by using the ``SECCOMP_ADDFD_FLAG_SEND`` flag and the return
+value will be the injected file descriptor number.
+
It is worth noting that ``struct seccomp_data`` contains the values of register
arguments to the syscall, but does not contain pointers to memory. The task's
memory is accessible to suitably privileged traces via ``ptrace()`` or
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index 6ba18b82a02e..78074254ab98 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -115,6 +115,7 @@ struct seccomp_notif_resp {
/* valid flags for seccomp_notif_addfd */
#define SECCOMP_ADDFD_FLAG_SETFD (1UL << 0) /* Specify remote fd */
+#define SECCOMP_ADDFD_FLAG_SEND (1UL << 1) /* Addfd and return it, atomically */
/**
* struct seccomp_notif_addfd
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 9f58049ac16d..057e17f3215d 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -107,6 +107,7 @@ struct seccomp_knotif {
* installing process should allocate the fd as normal.
* @flags: The flags for the new file descriptor. At the moment, only O_CLOEXEC
* is allowed.
+ * @ioctl_flags: The flags used for the seccomp_addfd ioctl.
* @ret: The return value of the installing process. It is set to the fd num
* upon success (>= 0).
* @completion: Indicates that the installing process has completed fd
@@ -118,6 +119,7 @@ struct seccomp_kaddfd {
struct file *file;
int fd;
unsigned int flags;
+ __u32 ioctl_flags;
union {
bool setfd;
@@ -1065,18 +1067,37 @@ static u64 seccomp_next_notify_id(struct seccomp_filter *filter)
return filter->notif->next_id++;
}
-static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd)
+static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_knotif *n)
{
+ int fd;
+
/*
* Remove the notification, and reset the list pointers, indicating
* that it has been handled.
*/
list_del_init(&addfd->list);
if (!addfd->setfd)
- addfd->ret = receive_fd(addfd->file, addfd->flags);
+ fd = receive_fd(addfd->file, addfd->flags);
else
- addfd->ret = receive_fd_replace(addfd->fd, addfd->file,
- addfd->flags);
+ fd = receive_fd_replace(addfd->fd, addfd->file, addfd->flags);
+ addfd->ret = fd;
+
+ if (addfd->ioctl_flags & SECCOMP_ADDFD_FLAG_SEND) {
+ /* If we fail reset and return an error to the notifier */
+ if (fd < 0) {
+ n->state = SECCOMP_NOTIFY_SENT;
+ } else {
+ /* Return the FD we just added */
+ n->flags = 0;
+ n->error = 0;
+ n->val = fd;
+ }
+ }
+
+ /*
+ * Mark the notification as completed. From this point, addfd mem
+ * might be invalidated and we can't safely read it anymore.
+ */
complete(&addfd->completion);
}
@@ -1120,7 +1141,7 @@ static int seccomp_do_user_notification(int this_syscall,
struct seccomp_kaddfd, list);
/* Check if we were woken up by a addfd message */
if (addfd)
- seccomp_handle_addfd(addfd);
+ seccomp_handle_addfd(addfd, &n);
} while (n.state != SECCOMP_NOTIFY_REPLIED);
@@ -1581,7 +1602,7 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
if (addfd.newfd_flags & ~O_CLOEXEC)
return -EINVAL;
- if (addfd.flags & ~SECCOMP_ADDFD_FLAG_SETFD)
+ if (addfd.flags & ~(SECCOMP_ADDFD_FLAG_SETFD | SECCOMP_ADDFD_FLAG_SEND))
return -EINVAL;
if (addfd.newfd && !(addfd.flags & SECCOMP_ADDFD_FLAG_SETFD))
@@ -1591,6 +1612,7 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
if (!kaddfd.file)
return -EBADF;
+ kaddfd.ioctl_flags = addfd.flags;
kaddfd.flags = addfd.newfd_flags;
kaddfd.setfd = addfd.flags & SECCOMP_ADDFD_FLAG_SETFD;
kaddfd.fd = addfd.newfd;
@@ -1616,6 +1638,23 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
goto out_unlock;
}
+ if (addfd.flags & SECCOMP_ADDFD_FLAG_SEND) {
+ /*
+ * Disallow queuing an atomic addfd + send reply while there are
+ * some addfd requests still to process.
+ *
+ * There is no clear reason to support it and allows us to keep
+ * the loop on the other side straight-forward.
+ */
+ if (!list_empty(&knotif->addfd)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ /* Allow exactly only one reply */
+ knotif->state = SECCOMP_NOTIFY_REPLIED;
+ }
+
list_add(&kaddfd.list, &knotif->addfd);
complete(&knotif->ready);
mutex_unlock(&filter->notify_lock);
--
2.30.2
prev parent reply other threads:[~2021-07-04 23:07 UTC|newest]
Thread overview: 89+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-04 23:02 [PATCH AUTOSEL 5.13 01/85] spi: Make of_register_spi_device also set the fwnode Sasha Levin
2021-07-04 23:02 ` [PATCH AUTOSEL 5.13 02/85] Add a reference to ucounts for each cred Sasha Levin
2021-07-04 23:02 ` [PATCH AUTOSEL 5.13 03/85] staging: media: rkvdec: fix pm_runtime_get_sync() usage count Sasha Levin
2021-07-04 23:02 ` [PATCH AUTOSEL 5.13 04/85] media: i2c: imx334: fix the pm runtime get logic Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 05/85] media: marvel-ccic: fix some issues when getting pm_runtime Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 06/85] media: mdk-mdp: fix pm_runtime_get_sync() usage count Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 07/85] media: s5p: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 08/85] media: am437x: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 09/85] media: sh_vou: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 10/85] media: mtk-vcodec: fix PM runtime get logic Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 11/85] media: s5p-jpeg: fix pm_runtime_get_sync() usage count Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 12/85] media: sunxi: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 13/85] media: sti/bdisp: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 14/85] media: exynos4-is: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 15/85] media: exynos-gsc: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 16/85] spi: spi-loopback-test: Fix 'tx_buf' might be 'rx_buf' Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 17/85] spi: spi-topcliff-pch: Fix potential double free in pch_spi_process_messages() Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 18/85] spi: omap-100k: Fix the length judgment problem Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 19/85] regulator: uniphier: Add missing MODULE_DEVICE_TABLE Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 20/85] sched/core: Initialize the idle task with preemption disabled Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 21/85] hwrng: exynos - Fix runtime PM imbalance on error Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 22/85] crypto: nx - add missing MODULE_DEVICE_TABLE Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 23/85] regmap-i2c: Set regmap max raw r/w from quirks Sasha Levin
2021-07-05 12:09 ` Mark Brown
2021-07-09 14:02 ` Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 24/85] media: sti: fix obj-$(config) targets Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 25/85] sched: Make the idle task quack like a per-CPU kthread Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 26/85] media: cpia2: fix memory leak in cpia2_usb_probe Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 27/85] media: cobalt: fix race condition in setting HPD Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 28/85] media: hevc: Fix dependent slice segment flags Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 29/85] media: pvrusb2: fix warning in pvr2_i2c_core_done Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 30/85] media: imx: imx7_mipi_csis: Fix logging of only error event counters Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 31/85] crypto: qat - check return code of qat_hal_rd_rel_reg() Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 32/85] crypto: qat - remove unused macro in FW loader Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 33/85] crypto: qce: skcipher: Fix incorrect sg count for dma transfers Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 34/85] crypto: ecdh - fix ecdh-nist-p192's entry in testmgr Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 35/85] crypto: ecdh - fix 'ecdh_init' Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 36/85] arm64: perf: Convert snprintf to sysfs_emit Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 37/85] sched/fair: Fix ascii art by relpacing tabs Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 38/85] ima: Don't remove security.ima if file must not be appraised Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 39/85] media: i2c: ov2659: Use clk_{prepare_enable,disable_unprepare}() to set xvclk on/off Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 40/85] media: bt878: do not schedule tasklet when it is not setup Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 41/85] media: em28xx: Fix possible memory leak of em28xx struct Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 42/85] media: hantro: Fix .buf_prepare Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 43/85] media: cedrus: " Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 44/85] media: v4l2-core: Avoid the dangling pointer in v4l2_fh_release Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 45/85] media: bt8xx: Fix a missing check bug in bt878_probe Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 46/85] media: st-hva: Fix potential NULL pointer dereferences Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 47/85] crypto: hisilicon/sec - fixup 3des minimum key size declaration Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 48/85] arm64: entry: don't instrument entry code with KCOV Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 49/85] Makefile: fix GDB warning with CONFIG_RELR Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 50/85] media: dvd_usb: memory leak in cinergyt2_fe_attach Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 51/85] memstick: rtsx_usb_ms: fix UAF Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 52/85] mmc: sdhci-sprd: use sdhci_sprd_writew Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 53/85] mmc: via-sdmmc: add a check against NULL pointer dereference Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 54/85] mmc: sdhci-of-aspeed: Turn down a phase correction warning Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 55/85] spi: meson-spicc: fix a wrong goto jump for avoiding memory leak Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 56/85] spi: meson-spicc: fix memory leak in meson_spicc_probe Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 57/85] regulator: mt6315: Fix checking return value of devm_regmap_init_spmi_ext Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 58/85] crypto: shash - avoid comparing pointers to exported functions under CFI Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 59/85] media: dvb_net: avoid speculation from net slot Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 60/85] media: dvbdev: fix error logic at dvb_register_device() Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 61/85] media: siano: fix device register error path Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 62/85] media: imx-csi: Skip first few frames from a BT.656 source Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 63/85] hwmon: (max31790) Report correct current pwm duty cycles Sasha Levin
2021-07-04 23:03 ` [PATCH AUTOSEL 5.13 64/85] hwmon: (max31790) Fix pwmX_enable attributes Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 65/85] sched/fair: Take thermal pressure into account while estimating energy Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 66/85] perf/x86: Reset the dirty counter to prevent the leak for an RDPMC task Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 67/85] drivers/perf: fix the missed ida_simple_remove() in ddr_perf_probe() Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 68/85] KVM: arm64: Restore PMU configuration on first run Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 69/85] KVM: PPC: Book3S HV: Fix TLB management on SMT8 POWER9 and POWER10 processors Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 70/85] btrfs: fix error handling in __btrfs_update_delayed_inode Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 71/85] btrfs: abort transaction if we fail to update the delayed inode Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 72/85] btrfs: always abort the transaction if we abort a trans handle Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 73/85] btrfs: sysfs: fix format string for some discard stats Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 74/85] btrfs: scrub: fix subpage repair error caused by hard coded PAGE_SIZE Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 75/85] btrfs: make Private2 lifespan more consistent Sasha Levin
2021-07-07 11:10 ` David Sterba
2021-07-08 11:09 ` Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 76/85] btrfs: fix the filemap_range_has_page() call in btrfs_punch_hole_lock_range() Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 77/85] btrfs: don't clear page extent mapped if we're not invalidating the full page Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 78/85] btrfs: disable build on platforms having page size 256K Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 79/85] locking/lockdep: Fix the dep path printing for backwards BFS Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 80/85] lockding/lockdep: Avoid to find wrong lock dep path in check_irq_usage() Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 81/85] KVM: s390: get rid of register asm usage Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 82/85] regulator: mt6358: Fix vdram2 .vsel_mask Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 83/85] regulator: da9052: Ensure enough delay time for .set_voltage_time_sel Sasha Levin
2021-07-04 23:04 ` [PATCH AUTOSEL 5.13 84/85] media: Fix Media Controller API config checks Sasha Levin
2021-07-04 23:04 ` Sasha Levin [this message]
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=20210704230420.1488358-85-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=christian.brauner@ubuntu.com \
--cc=keescook@chromium.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rodrigo@kinvolk.io \
--cc=sargun@sargun.me \
--cc=stable@vger.kernel.org \
--cc=tycho@tycho.pizza \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).