From: "Mike Frysinger" <vapier@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/sandbox:master commit in: libsandbox/wrapper-funcs/, tests/, libsandbox/
Date: Fri, 11 Sep 2015 07:53:28 +0000 (UTC) [thread overview]
Message-ID: <1361597322.87f753cf677137f8d6c06c56ee6cc4db11ec71b0.vapier@gentoo> (raw)
commit: 87f753cf677137f8d6c06c56ee6cc4db11ec71b0
Author: Mike Frysinger <vapier <AT> gentoo <DOT> org>
AuthorDate: Sat Feb 23 05:28:42 2013 +0000
Commit: Mike Frysinger <vapier <AT> gentoo <DOT> org>
CommitDate: Sat Feb 23 05:28:42 2013 +0000
URL: https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=87f753cf
libsandbox: preserve more SANDBOX env vars
While we took pains to preserve the LD_PRELOAD setting, this doesn't
help us too much in practice. If a process is going out of its way
to blow away LD_PRELOAD, chances are good it's blowing away all vars
it doesn't know about. That means all of our SANDBOX_XXX settings.
Since a preloaded libsandbox.so is useless w/out its SANDBOX_XXX
env vars, make sure we preserve those as well.
These changes also imply some behavioral differences from older
versions. Previously, you could `unset` a sandbox var in order
to disable it. That no longer works. If you wish to disable
things, you have to explicitly set it to "".
Signed-off-by: Mike Frysinger <vapier <AT> gentoo.org>
libsandbox/libsandbox.c | 284 ++++++++++++++++++++++++------
libsandbox/libsandbox.h | 2 +
libsandbox/wrapper-funcs/__wrapper_exec.c | 84 +--------
tests/Makefile.am | 2 +-
tests/mkdir-3.sh | 2 +-
tests/script-10.sh | 21 +++
tests/script.at | 1 +
7 files changed, 262 insertions(+), 134 deletions(-)
diff --git a/libsandbox/libsandbox.c b/libsandbox/libsandbox.c
index 5d9a796..4f4589f 100644
--- a/libsandbox/libsandbox.c
+++ b/libsandbox/libsandbox.c
@@ -28,7 +28,8 @@
char sandbox_lib[SB_PATH_MAX];
typedef struct {
- bool show_access_violation;
+ bool show_access_violation, on, active, testing, verbose, debug;
+ char *ld_library_path;
char **prefixes[5];
int num_prefixes[5];
#define deny_prefixes prefixes[0]
@@ -43,6 +44,7 @@ typedef struct {
#define num_write_denied_prefixes num_prefixes[4]
#define MAX_DYN_PREFIXES 4 /* the first 4 are dynamic */
} sbcontext_t;
+static sbcontext_t sbcontext;
static char *cached_env_vars[MAX_DYN_PREFIXES];
static char log_path[SB_PATH_MAX];
@@ -57,8 +59,7 @@ FILE *(*sbio_popen)(const char *, const char *) = sb_unwrapped_popen;
static char *resolve_path(const char *, int);
static int check_prefixes(char **, int, const char *);
static void clean_env_entries(char ***, int *);
-static void init_context(sbcontext_t *);
-static void init_env_entries(char ***, int *, const char *, const char *, int);
+static void sb_process_env_settings(void);
const char *sbio_message_path;
const char sbio_fallback_path[] = "/dev/tty";
@@ -84,6 +85,20 @@ void libsb_init(void)
get_sandbox_debug_log(debug_log_path, NULL);
get_sandbox_message_path(message_path);
sbio_message_path = message_path;
+
+ memset(&sbcontext, 0x00, sizeof(sbcontext));
+ sbcontext.show_access_violation = true;
+
+ sb_process_env_settings();
+ is_sandbox_on();
+ sbcontext.verbose = is_env_on(ENV_SANDBOX_VERBOSE);
+ sbcontext.debug = is_env_on(ENV_SANDBOX_DEBUG);
+ sbcontext.testing = is_env_on(ENV_SANDBOX_TESTING);
+ if (sbcontext.testing) {
+ const char *ldpath = getenv("LD_LIBRARY_PATH");
+ if (ldpath)
+ sbcontext.ld_library_path = xstrdup(ldpath);
+ }
}
/* resolve_dirfd_path - get the path relative to a dirfd
@@ -445,12 +460,6 @@ static bool write_logfile(const char *logfile, const char *func, const char *pat
return ret;
}
-static void init_context(sbcontext_t *context)
-{
- memset(context, 0x00, sizeof(*context));
- context->show_access_violation = true;
-}
-
static void clean_env_entries(char ***prefixes_array, int *prefixes_num)
{
if (*prefixes_array == NULL)
@@ -555,6 +564,43 @@ done:
return;
}
+static void sb_process_env_settings(void)
+{
+ static const char * const sb_env_names[4] = {
+ ENV_SANDBOX_DENY,
+ ENV_SANDBOX_READ,
+ ENV_SANDBOX_WRITE,
+ ENV_SANDBOX_PREDICT,
+ };
+
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(sb_env_names); ++i) {
+ char *sb_env = getenv(sb_env_names[i]);
+
+ /* Allow the vars to change values, but not be unset.
+ * See sb_check_envp() for more details. */
+ if (!sb_env)
+ continue;
+
+ if (/*(!sb_env && cached_env_vars[i]) || -- see above */
+ !cached_env_vars[i] ||
+ strcmp(cached_env_vars[i], sb_env) != 0)
+ {
+ clean_env_entries(&sbcontext.prefixes[i], &sbcontext.num_prefixes[i]);
+
+ if (cached_env_vars[i])
+ free(cached_env_vars[i]);
+
+ if (sb_env) {
+ init_env_entries(&sbcontext.prefixes[i], &sbcontext.num_prefixes[i],
+ sb_env_names[i], sb_env, 1);
+ cached_env_vars[i] = xstrdup(sb_env);
+ } else
+ cached_env_vars[i] = NULL;
+ }
+ }
+}
+
static int check_prefixes(char **prefixes, int num_prefixes, const char *path)
{
if (!prefixes)
@@ -835,15 +881,19 @@ static int check_syscall(sbcontext_t *sbcontext, int sb_nr, const char *func,
char *resolved_path = NULL;
int old_errno = errno;
int result;
- bool access, debug, verbose;
+ bool access, debug, verbose, set;
absolute_path = resolve_path(file, 0);
resolved_path = resolve_path(file, 1);
if (!absolute_path || !resolved_path)
goto error;
- verbose = is_env_on(ENV_SANDBOX_VERBOSE);
- debug = is_env_on(ENV_SANDBOX_DEBUG);
+ verbose = is_env_set_on(ENV_SANDBOX_VERBOSE, &set);
+ if (set)
+ sbcontext->verbose = verbose;
+ debug = is_env_set_on(ENV_SANDBOX_DEBUG, &set);
+ if (set)
+ sbcontext->debug = debug;
result = check_access(sbcontext, sb_nr, func, flags, absolute_path, resolved_path);
@@ -905,7 +955,7 @@ static int check_syscall(sbcontext_t *sbcontext, int sb_nr, const char *func,
bool is_sandbox_on(void)
{
- bool result;
+ bool result = false;
save_errno();
/* $SANDBOX_ACTIVE is an env variable that should ONLY
@@ -914,14 +964,24 @@ bool is_sandbox_on(void)
* in some cases when run in parallel with another sandbox,
* but not even in the sandbox shell.
*/
- char *sb_env_active = getenv(ENV_SANDBOX_ACTIVE);
- if (sandbox_on &&
- sb_env_active &&
- is_env_on(ENV_SANDBOX_ON) &&
- (0 == strcmp(sb_env_active, SANDBOX_ACTIVE)))
- result = true;
- else
- result = false;
+ if (sandbox_on) {
+ if (!sbcontext.active) {
+ /* Once you go active, you never go back */
+ char *sb_env_active = getenv(ENV_SANDBOX_ACTIVE);
+ sbcontext.active = (sb_env_active && !strcmp(sb_env_active, SANDBOX_ACTIVE));
+ }
+
+ if (sbcontext.active) {
+ bool on, set;
+ on = is_env_set_on(ENV_SANDBOX_ON, &set);
+ if (set)
+ sbcontext.on = on;
+
+ if (sbcontext.on)
+ result = true;
+ }
+ }
+
restore_errno();
return result;
}
@@ -929,7 +989,6 @@ bool is_sandbox_on(void)
bool before_syscall(int dirfd, int sb_nr, const char *func, const char *file, int flags)
{
int result;
- static sbcontext_t sbcontext;
char at_file_buf[SB_PATH_MAX];
/* Some funcs operate on a fd directly and so filename is NULL, but
@@ -962,39 +1021,10 @@ bool before_syscall(int dirfd, int sb_nr, const char *func, const char *file, in
if (!sb_init) {
libsb_init();
- init_context(&sbcontext);
sb_init = true;
}
- char *sb_env_names[4] = {
- ENV_SANDBOX_DENY,
- ENV_SANDBOX_READ,
- ENV_SANDBOX_WRITE,
- ENV_SANDBOX_PREDICT,
- };
-
- size_t i;
- for (i = 0; i < ARRAY_SIZE(sb_env_names); ++i) {
- char *sb_env = getenv(sb_env_names[i]);
-
- if ((!sb_env && cached_env_vars[i]) ||
- !cached_env_vars[i] ||
- strcmp(cached_env_vars[i], sb_env) != 0)
- {
- clean_env_entries(&(sbcontext.prefixes[i]),
- &(sbcontext.num_prefixes[i]));
-
- if (cached_env_vars[i])
- free(cached_env_vars[i]);
-
- if (sb_env) {
- init_env_entries(&(sbcontext.prefixes[i]),
- &(sbcontext.num_prefixes[i]), sb_env_names[i], sb_env, 1);
- cached_env_vars[i] = xstrdup(sb_env);
- } else
- cached_env_vars[i] = NULL;
- }
- }
+ sb_process_env_settings();
/* Might have been reset in check_access() */
sbcontext.show_access_violation = true;
@@ -1048,3 +1078,153 @@ bool before_syscall_open_char(int dirfd, int sb_nr, const char *func, const char
sb_nr = SB_NR_OPEN_WR, ext_func = "open_wr";
return before_syscall(dirfd, sb_nr, ext_func, file, 0);
}
+
+typedef struct {
+ const char *name;
+ size_t len;
+ char *value;
+} env_pair;
+#define ENV_PAIR(x, n, v) [x] = { .name = n, .len = sizeof(n) - 1, .value = v, }
+
+#define str_list_add_item_env(_string_list, _var, _item, _error) \
+ do { \
+ char *str = xmalloc(strlen(_var) + strlen(_item) + 2); \
+ sprintf(str, "%s=%s", _var, _item); \
+ str_list_add_item(_string_list, str, _error); \
+ } while (0)
+/* We need to make sure we pass along sandbox env vars. If we don't, programs
+ * (like scons) will inadvertently disable us. While we allow modification
+ * (e.g. export SANDBOX_WRITE=""), we disallow clearing (e.g. unset SANDBOX_WRITE).
+ * The former is clear in the user's intention, but the latter is indicative
+ * of a bad program.
+ *
+ * XXX: Might be much nicer if we could serialize these vars behind the back of
+ * the program. Might be hard to handle LD_PRELOAD though ...
+ */
+char **sb_check_envp(char **envp, size_t *mod_cnt)
+{
+ char **my_env;
+ char *entry;
+ size_t count, i;
+ env_pair vars[] = {
+ /* Indices matter -- see init below */
+ ENV_PAIR( 0, ENV_LD_PRELOAD, sandbox_lib),
+ ENV_PAIR( 1, ENV_SANDBOX_LOG, log_path),
+ ENV_PAIR( 2, ENV_SANDBOX_DEBUG_LOG, debug_log_path),
+ ENV_PAIR( 3, ENV_SANDBOX_MESSAGE_PATH, message_path),
+ ENV_PAIR( 4, ENV_SANDBOX_DENY, cached_env_vars[0]),
+ ENV_PAIR( 5, ENV_SANDBOX_READ, cached_env_vars[1]),
+ ENV_PAIR( 6, ENV_SANDBOX_WRITE, cached_env_vars[2]),
+ ENV_PAIR( 7, ENV_SANDBOX_PREDICT, cached_env_vars[3]),
+ ENV_PAIR( 8, ENV_SANDBOX_ON, NULL),
+ ENV_PAIR( 9, ENV_SANDBOX_ACTIVE, NULL),
+ ENV_PAIR(10, ENV_SANDBOX_VERBOSE, NULL),
+ ENV_PAIR(11, ENV_SANDBOX_DEBUG, NULL),
+ ENV_PAIR(12, "LD_LIBRARY_PATH", NULL),
+ ENV_PAIR(13, ENV_SANDBOX_TESTING, NULL),
+ };
+ size_t num_vars = ARRAY_SIZE(vars);
+ char *found_vars[num_vars];
+ size_t found_var_cnt;
+
+ /* First figure out how many vars are already in the env */
+ found_var_cnt = 0;
+ memset(found_vars, 0, sizeof(found_vars));
+
+ str_list_for_each_item(envp, entry, count) {
+ for (i = 0; i < num_vars; ++i) {
+ if (found_vars[i])
+ continue;
+ if (unlikely(!is_env_var(entry, vars[i].name, vars[i].len)))
+ continue;
+ found_vars[i] = entry;
+ ++found_var_cnt;
+ }
+ }
+
+ /* Now specially handle merging of LD_PRELOAD */
+ char *ld_preload;
+ bool merge_ld_preload = found_vars[0] && !strstr(found_vars[0], sandbox_lib);
+ if (unlikely(merge_ld_preload)) {
+ /* Ok, there's an existing LD_PRELOAD value that we need to merge
+ * with. Handle this specially. */
+ size_t ld_preload_len = strlen(ENV_LD_PRELOAD);
+ count = ld_preload_len + 1 + strlen(sandbox_lib) + 1 +
+ strlen(found_vars[0] + ld_preload_len + 1);
+ ld_preload = xmalloc(count * sizeof(char));
+ sprintf(ld_preload, "%s=%s %s", ENV_LD_PRELOAD, sandbox_lib,
+ found_vars[0] + ld_preload_len + 1);
+ goto mod_env;
+ }
+
+ /* If we found everything, there's nothing to do! */
+ if (num_vars == found_var_cnt)
+ /* Use the user's envp */
+ return envp;
+
+ /* Ok, we need to create our own envp, as we need to restore stuff
+ * and we should not touch the user's envp. First we add our vars,
+ * and just all the rest. */
+ mod_env:
+ /* Indices matter -- see vars[] setup above */
+ if (sbcontext.on)
+ vars[8].value = "1";
+ if (sbcontext.active)
+ vars[9].value = SANDBOX_ACTIVE;
+ if (sbcontext.verbose)
+ vars[10].value = "1";
+ if (sbcontext.debug)
+ vars[11].value = "1";
+ if (sbcontext.testing) {
+ vars[12].value = sbcontext.ld_library_path;
+ vars[13].value = "1";
+ }
+
+ my_env = NULL;
+ if (mod_cnt) {
+ /* Count directly due to variability with LD_PRELOAD and the value
+ * logic below. Getting out of sync can mean memory corruption. */
+ *mod_cnt = 0;
+ if (unlikely(merge_ld_preload)) {
+ str_list_add_item(my_env, ld_preload, error);
+ (*mod_cnt)++;
+ }
+ for (i = 0; i < num_vars; ++i) {
+ if (found_vars[i] || !vars[i].value)
+ continue;
+ str_list_add_item_env(my_env, vars[i].name, vars[i].value, error);
+ (*mod_cnt)++;
+ }
+
+ str_list_for_each_item(envp, entry, count) {
+ if (unlikely(merge_ld_preload && is_env_var(entry, vars[0].name, vars[0].len)))
+ continue;
+ str_list_add_item(my_env, entry, error);
+ }
+ } else {
+ if (unlikely(merge_ld_preload))
+ putenv(ld_preload);
+ for (i = 0; i < num_vars; ++i) {
+ if (found_vars[i] || !vars[i].value)
+ continue;
+ setenv(vars[i].name, vars[i].value, 1);
+ }
+ }
+
+ error:
+ return my_env;
+}
+
+void sb_cleanup_envp(char **envp, size_t mod_cnt)
+{
+ /* We assume all the stuffed vars are at the start */
+ size_t i;
+ for (i = 0; i < mod_cnt; ++i)
+ free(envp[i]);
+
+ /* We do not use str_list_free(), as we did not allocate the
+ * entries except for LD_PRELOAD. All the other entries are
+ * pointers to existing envp memory.
+ */
+ free(envp);
+}
diff --git a/libsandbox/libsandbox.h b/libsandbox/libsandbox.h
index 76dd8c8..596084d 100644
--- a/libsandbox/libsandbox.h
+++ b/libsandbox/libsandbox.h
@@ -56,6 +56,8 @@ void *get_dlsym(const char *symname, const char *symver);
extern char sandbox_lib[SB_PATH_MAX];
extern bool sandbox_on;
+char **sb_check_envp(char **envp, size_t *mod_cnt);
+void sb_cleanup_envp(char **envp, size_t mod_cnt);
extern pid_t trace_pid;
extern void sb_lock(void);
diff --git a/libsandbox/wrapper-funcs/__wrapper_exec.c b/libsandbox/wrapper-funcs/__wrapper_exec.c
index 3ac936c..7107c21 100644
--- a/libsandbox/wrapper-funcs/__wrapper_exec.c
+++ b/libsandbox/wrapper-funcs/__wrapper_exec.c
@@ -92,79 +92,6 @@ static void sb_check_exec(const char *filename, char *const argv[])
trace_main(filename, argv);
}
-static char **_sb_check_envp(char **envp, bool is_environ)
-{
- char **my_env = NULL;
- char *entry;
- char *ld_preload = NULL;
- char *old_ld_preload = NULL;
- size_t count, ld_preload_len;
-
- ld_preload_len = strlen(ENV_LD_PRELOAD);
- str_list_for_each_item(envp, entry, count) {
- if (!is_env_var(entry, ENV_LD_PRELOAD, ld_preload_len))
- continue;
-
- /* Check if we do not have to do anything */
- if (NULL != strstr(entry, sandbox_lib)) {
- /* Use the user's envp */
- return envp;
- } else {
- /* No need to continue (assuming the env is sane and does not
- * include multiple entries for same var); we have to modify
- * LD_PRELOAD to include our sandbox overrides
- */
- old_ld_preload = entry;
- break;
- }
- }
-
- /* Ok, we need to create our own envp, as we need to add LD_PRELOAD,
- * and we should not touch the user's envp. First we add LD_PRELOAD,
- * and just all the rest. */
- count = ld_preload_len + 1 + strlen(sandbox_lib) + 1 +
- (old_ld_preload ? strlen(old_ld_preload) - ld_preload_len : 0);
- ld_preload = xmalloc(count * sizeof(char));
- snprintf(ld_preload, count, "%s=%s%s%s", ENV_LD_PRELOAD, sandbox_lib,
- (old_ld_preload) ? " " : "",
- (old_ld_preload) ? old_ld_preload + ld_preload_len + 1 : "");
-
- if (!is_environ) {
- str_list_add_item(my_env, ld_preload, error);
-
- str_list_for_each_item(envp, entry, count) {
- if (!is_env_var(entry, ENV_LD_PRELOAD, ld_preload_len)) {
- str_list_add_item(my_env, entry, error);
- continue;
- }
- }
- } else
- putenv(ld_preload);
-
- error:
- return my_env;
-}
-static char **sb_check_envp(char **envp)
-{
- return _sb_check_envp(envp, false);
-}
-static void sb_check_environ(void)
-{
- _sb_check_envp(environ, true);
-}
-
-static void sb_cleanup_envp(char **envp)
-{
- /* We assume the LD_PRELOAD is the first entry */
- free(envp[0]);
-
- /* We do not use str_list_free(), as we did not allocate the
- * entries except for LD_PRELOAD. All the other entries are
- * pointers to existing envp memory.
- */
- free(envp);
-}
-
#endif
attribute_hidden
@@ -181,10 +108,6 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
{
WRAPPER_RET_TYPE result = WRAPPER_RET_DEFAULT;
-#ifdef EXEC_MY_ENV
- char **my_env = (char **)envp;
-#endif
-
/* The C library may implement some exec funcs by calling other
* exec funcs. So we might get a little sandbox recursion going
* on. But this shouldn't cause a problem now should it ?
@@ -233,9 +156,10 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
#endif
#ifdef EXEC_MY_ENV
- my_env = sb_check_envp(my_env);
+ size_t mod_cnt;
+ char **my_env = sb_check_envp((char **)envp, &mod_cnt);
#else
- sb_check_environ();
+ sb_check_envp(environ, NULL);
#endif
restore_errno();
@@ -246,7 +170,7 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
#ifdef EXEC_MY_ENV
if (my_env != envp)
- sb_cleanup_envp(my_env);
+ sb_cleanup_envp(my_env, mod_cnt);
#endif
#ifdef EXEC_RECUR_CHECK
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1d32e2e..0f0c249 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -79,7 +79,7 @@ check_PROGRAMS = \
sigsuspend-zsh_static_tst
dist_check_SCRIPTS = \
- $(wildcard $(srcdir)/*-?.sh) \
+ $(wildcard $(srcdir)/*-[0-9]*.sh) \
script-0 \
trace-0
diff --git a/tests/mkdir-3.sh b/tests/mkdir-3.sh
index 10e8723..e5b7899 100755
--- a/tests/mkdir-3.sh
+++ b/tests/mkdir-3.sh
@@ -18,7 +18,7 @@ chmod a-rx ..
ln -s / root
# this should trigger a sb violation
-unset SANDBOX_PREDICT
+SANDBOX_PREDICT=""
(mkdir-0 -1 root/aksdfjasdfjaskdfjasdfla 0777)
chmod a+rx ..
diff --git a/tests/script-10.sh b/tests/script-10.sh
new file mode 100755
index 0000000..801730e
--- /dev/null
+++ b/tests/script-10.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# make sure all the SANDBOX env vars make it back in.
+[ "${at_xfail}" = "yes" ] && exit 77 # see script-0
+
+ret=0
+
+out=$(env -i env)
+for var in LOG DEBUG_LOG MESSAGE_PATH DENY READ WRITE PREDICT ON ACTIVE ; do
+ var="SANDBOX_${var}"
+ oval=$(env | grep "^${var}=" | sed 's:^[^=]*=::')
+
+ nval=$(echo "${out}" | sed -n "/^${var}=/s:[^=]*=::p")
+
+ [ "${nval}" != "${oval}" ] && echo "!!! MISMATCH !!!" && ret=1
+ echo "env [${var}]='${oval}'"
+ echo "env-i[${var}]='${nval}'"
+ [ "${nval}" != "${oval}" ] && echo "!!! MISMATCH !!!"
+ echo
+done
+
+exit ${ret}
diff --git a/tests/script.at b/tests/script.at
index b095ce1..93e370a 100644
--- a/tests/script.at
+++ b/tests/script.at
@@ -7,3 +7,4 @@ SB_CHECK(6,,,8)
SB_CHECK(7)
SB_CHECK(8)
SB_CHECK(9, [wait errpipe... done OK!])
+SB_CHECK(10)
next reply other threads:[~2015-09-11 7:53 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-09-11 7:53 Mike Frysinger [this message]
-- strict thread matches above, loose matches on Subject: below --
2016-03-29 12:24 [gentoo-commits] proj/sandbox:master commit in: libsandbox/wrapper-funcs/, tests/, libsandbox/ Mike Frysinger
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=1361597322.87f753cf677137f8d6c06c56ee6cc4db11ec71b0.vapier@gentoo \
--to=vapier@gentoo.org \
--cc=gentoo-commits@lists.gentoo.org \
--cc=gentoo-dev@lists.gentoo.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox