From: Christian Brauner <christian.brauner@ubuntu.com>
To: "Stéphane Graber" <stgraber@ubuntu.com>,
"Eric W. Biederman" <ebiederm@xmission.com>,
"Aleksa Sarai" <cyphar@cyphar.com>,
"Jann Horn" <jannh@google.com>
Cc: smbarber@chromium.org, Seth Forshee <seth.forshee@canonical.com>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Alexey Dobriyan <adobriyan@gmail.com>,
Serge Hallyn <serge@hallyn.com>, James Morris <jmorris@namei.org>,
Kees Cook <keescook@chromium.org>,
Jonathan Corbet <corbet@lwn.net>, Phil Estes <estesp@gmail.com>,
linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
containers@lists.linux-foundation.org,
linux-security-module@vger.kernel.org, linux-api@vger.kernel.org,
Christian Brauner <christian.brauner@ubuntu.com>
Subject: [PATCH v3 25/25] selftests: add simple fsid mapping selftests
Date: Tue, 18 Feb 2020 15:34:11 +0100 [thread overview]
Message-ID: <20200218143411.2389182-26-christian.brauner@ubuntu.com> (raw)
In-Reply-To: <20200218143411.2389182-1-christian.brauner@ubuntu.com>
- Verify that fsid mappings cannot be written when if mappings have been
written already.
- Set up an id mapping and an fsid mapping, create a file and compare ids in
child and parent user namespace.
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
/* v2 */
patch not present
/* v3 */
patch added
---
tools/testing/selftests/Makefile | 1 +
.../testing/selftests/user_namespace/Makefile | 11 +
.../selftests/user_namespace/test_fsid_map.c | 511 ++++++++++++++++++
3 files changed, 523 insertions(+)
create mode 100644 tools/testing/selftests/user_namespace/Makefile
create mode 100644 tools/testing/selftests/user_namespace/test_fsid_map.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 63430e2664c2..49dcd21d2be7 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -60,6 +60,7 @@ endif
TARGETS += tmpfs
TARGETS += tpm2
TARGETS += user
+TARGETS += user_namespace
TARGETS += vm
TARGETS += x86
TARGETS += zram
diff --git a/tools/testing/selftests/user_namespace/Makefile b/tools/testing/selftests/user_namespace/Makefile
new file mode 100644
index 000000000000..3f89896f3285
--- /dev/null
+++ b/tools/testing/selftests/user_namespace/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -Wall
+
+all:
+
+TEST_GEN_PROGS += test_fsid_map
+
+include ../lib.mk
+
+$(OUTPUT)/test_fsid_map: test_fsid_map.c ../clone3/clone3_selftests.h
+
diff --git a/tools/testing/selftests/user_namespace/test_fsid_map.c b/tools/testing/selftests/user_namespace/test_fsid_map.c
new file mode 100644
index 000000000000..e278f137ff55
--- /dev/null
+++ b/tools/testing/selftests/user_namespace/test_fsid_map.c
@@ -0,0 +1,511 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/sched.h>
+#include <sys/fsuid.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../kselftest.h"
+#include "../clone3/clone3_selftests.h"
+
+static int wait_for_pid(pid_t pid)
+{
+ int status, ret;
+
+again:
+ ret = waitpid(pid, &status, 0);
+ if (ret == -1) {
+ if (errno == EINTR)
+ goto again;
+
+ return -1;
+ }
+
+ if (!WIFEXITED(status))
+ return -1;
+
+ return WEXITSTATUS(status);
+}
+
+static int setid_userns_root(void)
+{
+ if (setuid(0))
+ return -1;
+ if (setgid(0))
+ return -1;
+
+ setfsuid(0);
+ setfsgid(0);
+
+ if (setfsuid(0))
+ return -1;
+
+ if (setfsgid(0))
+ return -1;
+
+ return 0;
+}
+
+enum idmap_type {
+ UID_MAP,
+ GID_MAP,
+ FSUID_MAP,
+ FSGID_MAP,
+};
+
+static ssize_t read_nointr(int fd, void *buf, size_t count)
+{
+ ssize_t ret;
+again:
+ ret = read(fd, buf, count);
+ if (ret < 0 && errno == EINTR)
+ goto again;
+
+ return ret;
+}
+
+static ssize_t write_nointr(int fd, const void *buf, size_t count)
+{
+ ssize_t ret;
+again:
+ ret = write(fd, buf, count);
+ if (ret < 0 && errno == EINTR)
+ goto again;
+
+ return ret;
+}
+
+static int write_id_mapping(enum idmap_type type, pid_t pid, const char *buf,
+ size_t buf_size)
+{
+ int fd;
+ int ret;
+ char path[4096];
+
+ switch (type) {
+ case UID_MAP:
+ ret = snprintf(path, sizeof(path), "/proc/%d/uid_map", pid);
+ break;
+ case GID_MAP:
+ ret = snprintf(path, sizeof(path), "/proc/%d/gid_map", pid);
+ break;
+ case FSUID_MAP:
+ ret = snprintf(path, sizeof(path), "/proc/%d/fsuid_map", pid);
+ break;
+ case FSGID_MAP:
+ ret = snprintf(path, sizeof(path), "/proc/%d/fsgid_map", pid);
+ break;
+ default:
+ return -1;
+ }
+ if (ret < 0 || ret >= sizeof(path))
+ return -E2BIG;
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ return -1;
+
+ ret = write_nointr(fd, buf, buf_size);
+ close(fd);
+ if (ret != buf_size)
+ return -1;
+
+ return 0;
+}
+
+const char id_map[] = "0 100000 100000";
+#define id_map_size (sizeof(id_map) - 1)
+
+const char fsid_map[] = "0 300000 100000";
+#define fsid_map_size (sizeof(fsid_map) - 1)
+
+int unix_send_fds_iov(int fd, int *sendfds, int num_sendfds, struct iovec *iov,
+ size_t iovlen)
+{
+ char *cmsgbuf = NULL;
+ int ret;
+ struct msghdr msg;
+ struct cmsghdr *cmsg = NULL;
+ size_t cmsgbufsize = CMSG_SPACE(num_sendfds * sizeof(int));
+
+ memset(&msg, 0, sizeof(msg));
+
+ cmsgbuf = malloc(cmsgbufsize);
+ if (!cmsgbuf) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = cmsgbufsize;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(num_sendfds * sizeof(int));
+
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ memcpy(CMSG_DATA(cmsg), sendfds, num_sendfds * sizeof(int));
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = iovlen;
+
+again:
+ ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
+ if (ret < 0)
+ if (errno == EINTR)
+ goto again;
+
+ free(cmsgbuf);
+ return ret;
+}
+
+static int unix_send_fds(int fd, int *sendfds, int num_sendfds, void *data,
+ size_t size)
+{
+ char buf[1] = {0};
+ struct iovec iov = {
+ .iov_base = data ? data : buf,
+ .iov_len = data ? size : sizeof(buf),
+ };
+ return unix_send_fds_iov(fd, sendfds, num_sendfds, &iov, 1);
+}
+
+static int unix_recv_fds_iov(int fd, int *recvfds, int num_recvfds,
+ struct iovec *iov, size_t iovlen)
+{
+ char *cmsgbuf = NULL;
+ int ret;
+ struct msghdr msg;
+ struct cmsghdr *cmsg = NULL;
+ size_t cmsgbufsize = CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(num_recvfds * sizeof(int));
+
+ memset(&msg, 0, sizeof(msg));
+
+ cmsgbuf = malloc(cmsgbufsize);
+ if (!cmsgbuf) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = cmsgbufsize;
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = iovlen;
+
+again:
+ ret = recvmsg(fd, &msg, 0);
+ if (ret < 0) {
+ if (errno == EINTR)
+ goto again;
+
+ goto out;
+ }
+ if (ret == 0)
+ goto out;
+
+ /*
+ * If SO_PASSCRED is set we will always get a ucred message.
+ */
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ memset(recvfds, -1, num_recvfds * sizeof(int));
+ if (cmsg &&
+ cmsg->cmsg_len == CMSG_LEN(num_recvfds * sizeof(int)) &&
+ cmsg->cmsg_level == SOL_SOCKET)
+ memcpy(recvfds, CMSG_DATA(cmsg), num_recvfds * sizeof(int));
+ break;
+ }
+
+out:
+ free(cmsgbuf);
+ return ret;
+}
+
+static int unix_recv_fds(int fd, int *recvfds, int num_recvfds, void *data,
+ size_t size)
+{
+ char buf[1] = {0};
+ struct iovec iov = {
+ .iov_base = data ? data : buf,
+ .iov_len = data ? size : sizeof(buf),
+ };
+ return unix_recv_fds_iov(fd, recvfds, num_recvfds, &iov, 1);
+}
+
+static bool has_expected_owner(int fd, uid_t uid, gid_t gid)
+{
+ int ret;
+ struct stat s;
+ ret = fstat(fd, &s);
+ return !ret && s.st_uid == uid && s.st_gid == gid;
+}
+
+static int make_file_cmp_owner(uid_t uid, gid_t gid)
+{
+ char template[] = P_tmpdir "/.fsid_map_test_XXXXXX";
+ int fd;
+
+ fd = mkstemp(template);
+ if (fd < 0)
+ return -1;
+ unlink(template);
+
+ if (!has_expected_owner(fd, uid, gid)) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static void test_id_maps_imply_fsid_maps(void)
+{
+ int fret = EXIT_FAILURE;
+ ssize_t ret;
+ int fd = -EBADF;
+ pid_t pid;
+ int ipc[2];
+ struct clone_args args = {
+ .flags = CLONE_NEWUSER,
+ .exit_signal = SIGCHLD,
+ };
+
+ ret = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc);
+ if (ret < 0)
+ ksft_exit_fail_msg("socketpair() failed\n");
+
+ pid = sys_clone3(&args, sizeof(args));
+ if (pid < 0) {
+ close(ipc[0]);
+ close(ipc[1]);
+ ksft_exit_fail_msg("clone3() failed\n");
+ }
+
+ if (pid == 0) {
+ int fd;
+ char buf;
+
+ close(ipc[1]);
+
+ ret = read_nointr(ipc[0], &buf, 1);
+ if (ret != 1)
+ ksft_exit_fail_msg("read_nointr() failed\n");
+
+ if (setid_userns_root())
+ ksft_exit_fail_msg("setid_userns_root() failed\n");
+
+ fd = make_file_cmp_owner(0, 0);
+ if (fd < 0)
+ ksft_exit_fail_msg("make_file_cmp_owner() failed\n");
+
+ if (unix_send_fds(ipc[0], &fd, 1, NULL, 0) < 0)
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+
+ exit(EXIT_SUCCESS);
+ }
+
+ close(ipc[0]);
+
+ ret = write_id_mapping(UID_MAP, pid, id_map, id_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ /* Must fail since a uid mapping has already been written. */
+ ret = write_id_mapping(FSUID_MAP, pid, fsid_map, fsid_map_size);
+ if (ret == 0) {
+ ksft_exit_fail_msg("unix_send_fds() succeeded\n");
+ goto kill_child;
+ }
+
+ ret = write_id_mapping(GID_MAP, pid, id_map, id_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ /* Must fail since a gid mapping has already been written. */
+ ret = write_id_mapping(FSGID_MAP, pid, fsid_map, fsid_map_size);
+ if (ret == 0) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ ret = write_nointr(ipc[1], "1", 1);
+ if (ret != 1) {
+ ksft_exit_fail_msg("write_nointr() failed\n");
+ goto kill_child;
+ }
+
+ if (unix_recv_fds(ipc[1], &fd, 1, NULL, 0) < 0) {
+ ksft_exit_fail_msg("unix_recv_fds() failed\n");
+ goto kill_child;
+ }
+
+ if (!has_expected_owner(fd, 100000, 100000)) {
+ ksft_exit_fail_msg("has_expected_owner() failed\n");
+ goto kill_child;
+ }
+
+ fret = EXIT_SUCCESS;
+
+wait_child:
+ ret = wait_for_pid(pid);
+ if (ret)
+ ksft_exit_fail_msg("wait_for_pid() failed\n");
+
+ if (fret == EXIT_SUCCESS)
+ return;
+ exit(fret);
+
+kill_child:
+ kill(pid, SIGKILL);
+ exit(EXIT_FAILURE);
+ goto wait_child;
+}
+
+static void test_fsid_maps_basic(void)
+{
+ int fret = EXIT_FAILURE;
+ ssize_t ret;
+ int fd = -EBADF;
+ pid_t pid;
+ int ipc[2];
+ struct clone_args args = {
+ .flags = CLONE_NEWUSER,
+ .exit_signal = SIGCHLD,
+ };
+
+ ret = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc);
+ if (ret < 0)
+ ksft_exit_fail_msg("socketpair() failed\n");
+
+ pid = sys_clone3(&args, sizeof(args));
+ if (pid < 0) {
+ close(ipc[0]);
+ close(ipc[1]);
+ ksft_exit_fail_msg("clone3() failed\n");
+ }
+
+ if (pid == 0) {
+ int fd;
+ char buf;
+
+ close(ipc[1]);
+
+ ret = read_nointr(ipc[0], &buf, 1);
+ if (ret != 1)
+ ksft_exit_fail_msg("read_nointr() failed\n");
+
+ if (setid_userns_root())
+ ksft_exit_fail_msg("setid_userns_root() failed\n");
+
+ fd = make_file_cmp_owner(0, 0);
+ if (fd < 0)
+ ksft_exit_fail_msg("make_file_cmp_owner() failed\n");
+
+ if (unix_send_fds(ipc[0], &fd, 1, NULL, 0) < 0)
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+
+ exit(EXIT_SUCCESS);
+ }
+
+ close(ipc[0]);
+
+ /* Must fail since a uid mapping has already been written. */
+ ret = write_id_mapping(FSUID_MAP, pid, fsid_map, fsid_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ ret = write_id_mapping(UID_MAP, pid, id_map, id_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ /* Must fail since a gid mapping has already been written. */
+ ret = write_id_mapping(FSGID_MAP, pid, fsid_map, fsid_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ ret = write_id_mapping(GID_MAP, pid, id_map, id_map_size);
+ if (ret) {
+ ksft_exit_fail_msg("unix_send_fds() failed\n");
+ goto kill_child;
+ }
+
+ ret = write_nointr(ipc[1], "1", 1);
+ if (ret != 1) {
+ ksft_exit_fail_msg("write_nointr() failed\n");
+ goto kill_child;
+ }
+
+ if (unix_recv_fds(ipc[1], &fd, 1, NULL, 0) < 0) {
+ ksft_exit_fail_msg("unix_recv_fds() failed\n");
+ goto kill_child;
+ }
+
+ if (!has_expected_owner(fd, 300000, 300000)) {
+ ksft_exit_fail_msg("has_expected_owner() failed\n");
+ goto kill_child;
+ }
+
+ fret = EXIT_SUCCESS;
+
+wait_child:
+ ret = wait_for_pid(pid);
+ if (ret)
+ ksft_exit_fail_msg("wait_for_pid() failed\n");
+
+ if (fret == EXIT_SUCCESS)
+ return;
+ exit(fret);
+
+kill_child:
+ kill(pid, SIGKILL);
+ exit(EXIT_FAILURE);
+ goto wait_child;
+}
+
+int main(int argc, char *argv[])
+{
+ if (getuid())
+ ksft_exit_skip("fsid mapping tests require root\n");
+
+ if (access("/proc/self/fsuid_map", F_OK))
+ ksft_exit_skip("fsid mappings not supported by this kernel\n");
+
+ test_clone3_supported();
+
+ test_id_maps_imply_fsid_maps();
+ test_fsid_maps_basic();
+
+ exit(EXIT_SUCCESS);
+}
--
2.25.0
next prev parent reply other threads:[~2020-02-18 14:38 UTC|newest]
Thread overview: 51+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-02-18 14:33 [PATCH v3 00/25] user_namespace: introduce fsid mappings Christian Brauner
2020-02-18 14:33 ` [PATCH v3 01/25] user_namespace: introduce fsid mappings infrastructure Christian Brauner
2020-02-19 2:33 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 02/25] proc: add /proc/<pid>/fsuid_map Christian Brauner
2020-02-19 2:33 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 03/25] proc: add /proc/<pid>/fsgid_map Christian Brauner
2020-02-19 2:33 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 04/25] fsuidgid: add fsid mapping helpers Christian Brauner
2020-02-18 14:33 ` [PATCH v3 05/25] user_namespace: refactor map_write() Christian Brauner
2020-02-19 2:35 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 06/25] user_namespace: make map_write() support fsid mappings Christian Brauner
2020-02-19 16:18 ` Jann Horn
2020-02-18 14:33 ` [PATCH v3 07/25] proc: task_state(): use from_kfs{g,u}id_munged Christian Brauner
2020-02-19 2:36 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 08/25] cred: add kfs{g,u}id Christian Brauner
2020-02-18 14:33 ` [PATCH v3 09/25] fs: add is_userns_visible() helper Christian Brauner
2020-02-19 2:42 ` Serge E. Hallyn
2020-02-19 12:06 ` Christian Brauner
2020-02-19 17:18 ` Andy Lutomirski
2020-02-20 14:26 ` Serge E. Hallyn
2020-02-18 14:33 ` [PATCH v3 10/25] namei: may_{o_}create(): handle fsid mappings Christian Brauner
2020-02-18 14:33 ` [PATCH v3 11/25] inode: inode_owner_or_capable(): " Christian Brauner
2020-02-18 22:25 ` Christoph Hellwig
2020-02-19 12:29 ` Christian Brauner
2020-02-18 14:33 ` [PATCH v3 12/25] capability: privileged_wrt_inode_uidgid(): " Christian Brauner
2020-02-18 14:33 ` [PATCH v3 13/25] stat: " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 14/25] open: " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 15/25] posix_acl: " Christian Brauner
2020-02-18 22:26 ` Christoph Hellwig
2020-02-19 12:56 ` Christian Brauner
2020-02-18 14:34 ` [PATCH v3 16/25] attr: notify_change(): " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 17/25] commoncap: cap_bprm_set_creds(): " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 18/25] commoncap: cap_task_fix_setuid(): " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 19/25] commoncap: handle fsid mappings with vfs caps Christian Brauner
2020-02-19 15:53 ` Jann Horn
2020-02-18 14:34 ` [PATCH v3 20/25] exec: bprm_fill_uid(): handle fsid mappings Christian Brauner
2020-02-18 14:34 ` [PATCH v3 21/25] ptrace: adapt ptrace_may_access() to always uses unmapped fsids Christian Brauner
2020-02-18 14:34 ` [PATCH v3 22/25] devpts: handle fsid mappings Christian Brauner
2020-02-18 14:34 ` [PATCH v3 23/25] keys: " Christian Brauner
2020-02-18 14:34 ` [PATCH v3 24/25] sys: handle fsid mappings in set*id() calls Christian Brauner
2020-02-19 15:42 ` Jann Horn
2020-02-18 14:34 ` Christian Brauner [this message]
2020-02-18 23:50 ` [PATCH v3 00/25] user_namespace: introduce fsid mappings James Bottomley
2020-02-19 12:27 ` Christian Brauner
2020-02-19 15:36 ` James Bottomley
2020-02-19 15:33 ` Jann Horn
2020-02-19 19:35 ` Serge E. Hallyn
2020-02-19 21:48 ` Serge E. Hallyn
2020-02-19 21:56 ` Tycho Andersen
2020-02-27 19:33 ` Josef Bacik
2020-03-02 14:34 ` Serge E. Hallyn
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=20200218143411.2389182-26-christian.brauner@ubuntu.com \
--to=christian.brauner@ubuntu.com \
--cc=adobriyan@gmail.com \
--cc=containers@lists.linux-foundation.org \
--cc=corbet@lwn.net \
--cc=cyphar@cyphar.com \
--cc=ebiederm@xmission.com \
--cc=estesp@gmail.com \
--cc=jannh@google.com \
--cc=jmorris@namei.org \
--cc=keescook@chromium.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=serge@hallyn.com \
--cc=seth.forshee@canonical.com \
--cc=smbarber@chromium.org \
--cc=stgraber@ubuntu.com \
--cc=viro@zeniv.linux.org.uk \
/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).