mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 13:53:31 +01:00
Extract process-management functionality into a new lib/process
Note that procmon does *not* go here, since procmon needs to integrate with the event loop.
This commit is contained in:
parent
ec4eee6356
commit
315e6b59dd
2
.gitignore
vendored
2
.gitignore
vendored
@ -191,6 +191,8 @@ uptime-*.json
|
|||||||
/src/lib/libtor-memarea-testing.a
|
/src/lib/libtor-memarea-testing.a
|
||||||
/src/lib/libtor-net.a
|
/src/lib/libtor-net.a
|
||||||
/src/lib/libtor-net-testing.a
|
/src/lib/libtor-net-testing.a
|
||||||
|
/src/lib/libtor-process.a
|
||||||
|
/src/lib/libtor-process-testing.a
|
||||||
/src/lib/libtor-sandbox.a
|
/src/lib/libtor-sandbox.a
|
||||||
/src/lib/libtor-sandbox-testing.a
|
/src/lib/libtor-sandbox-testing.a
|
||||||
/src/lib/libtor-string.a
|
/src/lib/libtor-string.a
|
||||||
|
@ -40,6 +40,7 @@ endif
|
|||||||
# "Common" libraries used to link tor's utility code.
|
# "Common" libraries used to link tor's utility code.
|
||||||
TOR_UTIL_LIBS = \
|
TOR_UTIL_LIBS = \
|
||||||
src/common/libor.a \
|
src/common/libor.a \
|
||||||
|
src/lib/libtor-process.a \
|
||||||
src/lib/libtor-fs.a \
|
src/lib/libtor-fs.a \
|
||||||
src/lib/libtor-encoding.a \
|
src/lib/libtor-encoding.a \
|
||||||
src/lib/libtor-sandbox.a \
|
src/lib/libtor-sandbox.a \
|
||||||
@ -62,6 +63,7 @@ TOR_UTIL_LIBS = \
|
|||||||
# and tests)
|
# and tests)
|
||||||
TOR_UTIL_TESTING_LIBS = \
|
TOR_UTIL_TESTING_LIBS = \
|
||||||
src/common/libor-testing.a \
|
src/common/libor-testing.a \
|
||||||
|
src/lib/libtor-process-testing.a \
|
||||||
src/lib/libtor-fs-testing.a \
|
src/lib/libtor-fs-testing.a \
|
||||||
src/lib/libtor-encoding-testing.a \
|
src/lib/libtor-encoding-testing.a \
|
||||||
src/lib/libtor-sandbox-testing.a \
|
src/lib/libtor-sandbox-testing.a \
|
||||||
|
@ -367,421 +367,6 @@ set_max_file_descriptors(rlim_t limit, int *max_out)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
/** Log details of current user and group credentials. Return 0 on
|
|
||||||
* success. Logs and return -1 on failure.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
log_credential_status(void)
|
|
||||||
{
|
|
||||||
/** Log level to use when describing non-error UID/GID status. */
|
|
||||||
#define CREDENTIAL_LOG_LEVEL LOG_INFO
|
|
||||||
/* Real, effective and saved UIDs */
|
|
||||||
uid_t ruid, euid, suid;
|
|
||||||
/* Read, effective and saved GIDs */
|
|
||||||
gid_t rgid, egid, sgid;
|
|
||||||
/* Supplementary groups */
|
|
||||||
gid_t *sup_gids = NULL;
|
|
||||||
int sup_gids_size;
|
|
||||||
/* Number of supplementary groups */
|
|
||||||
int ngids;
|
|
||||||
|
|
||||||
/* log UIDs */
|
|
||||||
#ifdef HAVE_GETRESUID
|
|
||||||
if (getresuid(&ruid, &euid, &suid) != 0 ) {
|
|
||||||
log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
|
||||||
"UID is %u (real), %u (effective), %u (saved)",
|
|
||||||
(unsigned)ruid, (unsigned)euid, (unsigned)suid);
|
|
||||||
}
|
|
||||||
#else /* !(defined(HAVE_GETRESUID)) */
|
|
||||||
/* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
|
|
||||||
ruid = getuid();
|
|
||||||
euid = geteuid();
|
|
||||||
(void)suid;
|
|
||||||
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
|
||||||
"UID is %u (real), %u (effective), unknown (saved)",
|
|
||||||
(unsigned)ruid, (unsigned)euid);
|
|
||||||
#endif /* defined(HAVE_GETRESUID) */
|
|
||||||
|
|
||||||
/* log GIDs */
|
|
||||||
#ifdef HAVE_GETRESGID
|
|
||||||
if (getresgid(&rgid, &egid, &sgid) != 0 ) {
|
|
||||||
log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
|
||||||
"GID is %u (real), %u (effective), %u (saved)",
|
|
||||||
(unsigned)rgid, (unsigned)egid, (unsigned)sgid);
|
|
||||||
}
|
|
||||||
#else /* !(defined(HAVE_GETRESGID)) */
|
|
||||||
/* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
|
|
||||||
rgid = getgid();
|
|
||||||
egid = getegid();
|
|
||||||
(void)sgid;
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
|
||||||
"GID is %u (real), %u (effective), unknown (saved)",
|
|
||||||
(unsigned)rgid, (unsigned)egid);
|
|
||||||
#endif /* defined(HAVE_GETRESGID) */
|
|
||||||
|
|
||||||
/* log supplementary groups */
|
|
||||||
sup_gids_size = 64;
|
|
||||||
sup_gids = tor_calloc(64, sizeof(gid_t));
|
|
||||||
while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
|
|
||||||
errno == EINVAL &&
|
|
||||||
sup_gids_size < NGROUPS_MAX) {
|
|
||||||
sup_gids_size *= 2;
|
|
||||||
sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ngids < 0) {
|
|
||||||
log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
|
|
||||||
strerror(errno));
|
|
||||||
tor_free(sup_gids);
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
int i, retval = 0;
|
|
||||||
char *s = NULL;
|
|
||||||
smartlist_t *elts = smartlist_new();
|
|
||||||
|
|
||||||
for (i = 0; i<ngids; i++) {
|
|
||||||
smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
s = smartlist_join_strings(elts, " ", 0, NULL);
|
|
||||||
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
|
|
||||||
|
|
||||||
tor_free(s);
|
|
||||||
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
|
|
||||||
smartlist_free(elts);
|
|
||||||
tor_free(sup_gids);
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif /* !defined(_WIN32) */
|
|
||||||
|
|
||||||
/** Return true iff we were compiled with capability support, and capabilities
|
|
||||||
* seem to work. **/
|
|
||||||
int
|
|
||||||
have_capability_support(void)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_LINUX_CAPABILITIES
|
|
||||||
cap_t caps = cap_get_proc();
|
|
||||||
if (caps == NULL)
|
|
||||||
return 0;
|
|
||||||
cap_free(caps);
|
|
||||||
return 1;
|
|
||||||
#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
|
|
||||||
return 0;
|
|
||||||
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_LINUX_CAPABILITIES
|
|
||||||
/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
|
|
||||||
* appropriate.
|
|
||||||
*
|
|
||||||
* If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
|
|
||||||
* CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
|
|
||||||
* setuid().
|
|
||||||
*
|
|
||||||
* If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
|
|
||||||
* PR_KEEPCAPS.
|
|
||||||
*
|
|
||||||
* Return 0 on success, and -1 on failure.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
drop_capabilities(int pre_setuid)
|
|
||||||
{
|
|
||||||
/* We keep these three capabilities, and these only, as we setuid.
|
|
||||||
* After we setuid, we drop all but the first. */
|
|
||||||
const cap_value_t caplist[] = {
|
|
||||||
CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
|
|
||||||
};
|
|
||||||
const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
|
|
||||||
const int n_effective = pre_setuid ? 3 : 1;
|
|
||||||
const int n_permitted = pre_setuid ? 3 : 1;
|
|
||||||
const int n_inheritable = 1;
|
|
||||||
const int keepcaps = pre_setuid ? 1 : 0;
|
|
||||||
|
|
||||||
/* Sets whether we keep capabilities across a setuid. */
|
|
||||||
if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
|
|
||||||
log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
|
|
||||||
where, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cap_t caps = cap_get_proc();
|
|
||||||
if (!caps) {
|
|
||||||
log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
|
|
||||||
where, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
cap_clear(caps);
|
|
||||||
|
|
||||||
cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
|
|
||||||
cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
|
|
||||||
cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
|
|
||||||
|
|
||||||
int r = cap_set_proc(caps);
|
|
||||||
cap_free(caps);
|
|
||||||
if (r < 0) {
|
|
||||||
log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
|
|
||||||
where, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
|
||||||
|
|
||||||
/** Call setuid and setgid to run as <b>user</b> and switch to their
|
|
||||||
* primary group. Return 0 on success. On failure, log and return -1.
|
|
||||||
*
|
|
||||||
* If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
|
|
||||||
* system to retain the abilitity to bind low ports.
|
|
||||||
*
|
|
||||||
* If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
|
|
||||||
* don't have capability support.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
switch_id(const char *user, const unsigned flags)
|
|
||||||
{
|
|
||||||
#ifndef _WIN32
|
|
||||||
const struct passwd *pw = NULL;
|
|
||||||
uid_t old_uid;
|
|
||||||
gid_t old_gid;
|
|
||||||
static int have_already_switched_id = 0;
|
|
||||||
const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
|
|
||||||
const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
|
|
||||||
|
|
||||||
tor_assert(user);
|
|
||||||
|
|
||||||
if (have_already_switched_id)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Log the initial credential state */
|
|
||||||
if (log_credential_status())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
|
|
||||||
|
|
||||||
/* Get old UID/GID to check if we changed correctly */
|
|
||||||
old_uid = getuid();
|
|
||||||
old_gid = getgid();
|
|
||||||
|
|
||||||
/* Lookup the user and group information, if we have a problem, bail out. */
|
|
||||||
pw = tor_getpwnam(user);
|
|
||||||
if (pw == NULL) {
|
|
||||||
log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_LINUX_CAPABILITIES
|
|
||||||
(void) warn_if_no_caps;
|
|
||||||
if (keep_bindlow) {
|
|
||||||
if (drop_capabilities(1))
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
|
|
||||||
(void) keep_bindlow;
|
|
||||||
if (warn_if_no_caps) {
|
|
||||||
log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
|
|
||||||
"on this system.");
|
|
||||||
}
|
|
||||||
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
|
||||||
|
|
||||||
/* Properly switch egid,gid,euid,uid here or bail out */
|
|
||||||
if (setgroups(1, &pw->pw_gid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
|
|
||||||
(int)pw->pw_gid, strerror(errno));
|
|
||||||
if (old_uid == pw->pw_uid) {
|
|
||||||
log_warn(LD_GENERAL, "Tor is already running as %s. You do not need "
|
|
||||||
"the \"User\" option if you are already running as the user "
|
|
||||||
"you want to be. (If you did not set the User option in your "
|
|
||||||
"torrc, check whether it was specified on the command line "
|
|
||||||
"by a startup script.)", user);
|
|
||||||
} else {
|
|
||||||
log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
|
|
||||||
" as root.");
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setegid(pw->pw_gid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting egid to %d: %s",
|
|
||||||
(int)pw->pw_gid, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setgid(pw->pw_gid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting gid to %d: %s",
|
|
||||||
(int)pw->pw_gid, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setuid(pw->pw_uid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
|
|
||||||
user, (int)pw->pw_uid, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seteuid(pw->pw_uid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
|
|
||||||
user, (int)pw->pw_uid, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is how OpenBSD rolls:
|
|
||||||
if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
|
|
||||||
setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
|
|
||||||
setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
|
|
||||||
log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
|
|
||||||
strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* We've properly switched egid, gid, euid, uid, and supplementary groups if
|
|
||||||
* we're here. */
|
|
||||||
#ifdef HAVE_LINUX_CAPABILITIES
|
|
||||||
if (keep_bindlow) {
|
|
||||||
if (drop_capabilities(0))
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
|
||||||
|
|
||||||
#if !defined(CYGWIN) && !defined(__CYGWIN__)
|
|
||||||
/* If we tried to drop privilege to a group/user other than root, attempt to
|
|
||||||
* restore root (E)(U|G)ID, and abort if the operation succeeds */
|
|
||||||
|
|
||||||
/* Only check for privilege dropping if we were asked to be non-root */
|
|
||||||
if (pw->pw_uid) {
|
|
||||||
/* Try changing GID/EGID */
|
|
||||||
if (pw->pw_gid != old_gid &&
|
|
||||||
(setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
|
|
||||||
log_warn(LD_GENERAL, "Was able to restore group credentials even after "
|
|
||||||
"switching GID: this means that the setgid code didn't work.");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try changing UID/EUID */
|
|
||||||
if (pw->pw_uid != old_uid &&
|
|
||||||
(setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
|
|
||||||
log_warn(LD_GENERAL, "Was able to restore user credentials even after "
|
|
||||||
"switching UID: this means that the setuid code didn't work.");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
|
|
||||||
|
|
||||||
/* Check what really happened */
|
|
||||||
if (log_credential_status()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
have_already_switched_id = 1; /* mark success so we never try again */
|
|
||||||
|
|
||||||
#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
|
|
||||||
defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
|
|
||||||
if (pw->pw_uid) {
|
|
||||||
/* Re-enable core dumps if we're not running as root. */
|
|
||||||
log_info(LD_CONFIG, "Re-enabling coredumps");
|
|
||||||
if (prctl(PR_SET_DUMPABLE, 1)) {
|
|
||||||
log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
#else /* !(!defined(_WIN32)) */
|
|
||||||
(void)user;
|
|
||||||
(void)flags;
|
|
||||||
|
|
||||||
log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
|
|
||||||
return -1;
|
|
||||||
#endif /* !defined(_WIN32) */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We only use the linux prctl for now. There is no Win32 support; this may
|
|
||||||
* also work on various BSD systems and Mac OS X - send testing feedback!
|
|
||||||
*
|
|
||||||
* On recent Gnu/Linux kernels it is possible to create a system-wide policy
|
|
||||||
* that will prevent non-root processes from attaching to other processes
|
|
||||||
* unless they are the parent process; thus gdb can attach to programs that
|
|
||||||
* they execute but they cannot attach to other processes running as the same
|
|
||||||
* user. The system wide policy may be set with the sysctl
|
|
||||||
* kernel.yama.ptrace_scope or by inspecting
|
|
||||||
* /proc/sys/kernel/yama/ptrace_scope and it is 1 by default on Ubuntu 11.04.
|
|
||||||
*
|
|
||||||
* This ptrace scope will be ignored on Gnu/Linux for users with
|
|
||||||
* CAP_SYS_PTRACE and so it is very likely that root will still be able to
|
|
||||||
* attach to the Tor process.
|
|
||||||
*/
|
|
||||||
/** Attempt to disable debugger attachment: return 1 on success, -1 on
|
|
||||||
* failure, and 0 if we don't know how to try on this platform. */
|
|
||||||
int
|
|
||||||
tor_disable_debugger_attach(void)
|
|
||||||
{
|
|
||||||
int r = -1;
|
|
||||||
log_debug(LD_CONFIG,
|
|
||||||
"Attemping to disable debugger attachment to Tor for "
|
|
||||||
"unprivileged users.");
|
|
||||||
#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \
|
|
||||||
&& defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
|
|
||||||
#define TRIED_TO_DISABLE
|
|
||||||
r = prctl(PR_SET_DUMPABLE, 0);
|
|
||||||
#elif defined(__APPLE__) && defined(PT_DENY_ATTACH)
|
|
||||||
#define TRIED_TO_ATTACH
|
|
||||||
r = ptrace(PT_DENY_ATTACH, 0, 0, 0);
|
|
||||||
#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */
|
|
||||||
|
|
||||||
// XXX: TODO - Mac OS X has dtrace and this may be disabled.
|
|
||||||
// XXX: TODO - Windows probably has something similar
|
|
||||||
#ifdef TRIED_TO_DISABLE
|
|
||||||
if (r == 0) {
|
|
||||||
log_debug(LD_CONFIG,"Debugger attachment disabled for "
|
|
||||||
"unprivileged users.");
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s",
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
#endif /* defined(TRIED_TO_DISABLE) */
|
|
||||||
#undef TRIED_TO_DISABLE
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef HAVE__NSGETENVIRON
|
|
||||||
#ifndef HAVE_EXTERN_ENVIRON_DECLARED
|
|
||||||
/* Some platforms declare environ under some circumstances, others don't. */
|
|
||||||
#ifndef RUNNING_DOXYGEN
|
|
||||||
extern char **environ;
|
|
||||||
#endif
|
|
||||||
#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */
|
|
||||||
#endif /* !defined(HAVE__NSGETENVIRON) */
|
|
||||||
|
|
||||||
/** Return the current environment. This is a portable replacement for
|
|
||||||
* 'environ'. */
|
|
||||||
char **
|
|
||||||
get_environment(void)
|
|
||||||
{
|
|
||||||
#ifdef HAVE__NSGETENVIRON
|
|
||||||
/* This is for compatibility between OSX versions. Otherwise (for example)
|
|
||||||
* when we do a mostly-static build on OSX 10.7, the resulting binary won't
|
|
||||||
* work on OSX 10.6. */
|
|
||||||
return *_NSGetEnviron();
|
|
||||||
#else /* !(defined(HAVE__NSGETENVIRON)) */
|
|
||||||
return environ;
|
|
||||||
#endif /* defined(HAVE__NSGETENVIRON) */
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get name of current host and write it to <b>name</b> array, whose
|
/** Get name of current host and write it to <b>name</b> array, whose
|
||||||
* length is specified by <b>namelen</b> argument. Return 0 upon
|
* length is specified by <b>namelen</b> argument. Return 0 upon
|
||||||
* successful completion; otherwise return return -1. (Currently,
|
* successful completion; otherwise return return -1. (Currently,
|
||||||
@ -965,93 +550,6 @@ compute_num_cpus(void)
|
|||||||
return num_cpus;
|
return num_cpus;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
|
|
||||||
#define HAVE_UNIX_MLOCKALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_UNIX_MLOCKALL
|
|
||||||
/** Attempt to raise the current and max rlimit to infinity for our process.
|
|
||||||
* This only needs to be done once and can probably only be done when we have
|
|
||||||
* not already dropped privileges.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
tor_set_max_memlock(void)
|
|
||||||
{
|
|
||||||
/* Future consideration for Windows is probably SetProcessWorkingSetSize
|
|
||||||
* This is similar to setting the memory rlimit of RLIMIT_MEMLOCK
|
|
||||||
* http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct rlimit limit;
|
|
||||||
|
|
||||||
/* RLIM_INFINITY is -1 on some platforms. */
|
|
||||||
limit.rlim_cur = RLIM_INFINITY;
|
|
||||||
limit.rlim_max = RLIM_INFINITY;
|
|
||||||
|
|
||||||
if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) {
|
|
||||||
if (errno == EPERM) {
|
|
||||||
log_warn(LD_GENERAL, "You appear to lack permissions to change memory "
|
|
||||||
"limits. Are you root?");
|
|
||||||
}
|
|
||||||
log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s",
|
|
||||||
strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif /* defined(HAVE_UNIX_MLOCKALL) */
|
|
||||||
|
|
||||||
/** Attempt to lock all current and all future memory pages.
|
|
||||||
* This should only be called once and while we're privileged.
|
|
||||||
* Like mlockall() we return 0 when we're successful and -1 when we're not.
|
|
||||||
* Unlike mlockall() we return 1 if we've already attempted to lock memory.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
tor_mlockall(void)
|
|
||||||
{
|
|
||||||
static int memory_lock_attempted = 0;
|
|
||||||
|
|
||||||
if (memory_lock_attempted) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
memory_lock_attempted = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Future consideration for Windows may be VirtualLock
|
|
||||||
* VirtualLock appears to implement mlock() but not mlockall()
|
|
||||||
*
|
|
||||||
* http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_UNIX_MLOCKALL
|
|
||||||
if (tor_set_max_memlock() == 0) {
|
|
||||||
log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) {
|
|
||||||
log_info(LD_GENERAL, "Insecure OS paging is effectively disabled.");
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
if (errno == ENOSYS) {
|
|
||||||
/* Apple - it's 2009! I'm looking at you. Grrr. */
|
|
||||||
log_notice(LD_GENERAL, "It appears that mlockall() is not available on "
|
|
||||||
"your platform.");
|
|
||||||
} else if (errno == EPERM) {
|
|
||||||
log_notice(LD_GENERAL, "It appears that you lack the permissions to "
|
|
||||||
"lock memory. Are you root?");
|
|
||||||
}
|
|
||||||
log_notice(LD_GENERAL, "Unable to lock all current and future memory "
|
|
||||||
"pages: %s", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#else /* !(defined(HAVE_UNIX_MLOCKALL)) */
|
|
||||||
log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?");
|
|
||||||
return -1;
|
|
||||||
#endif /* defined(HAVE_UNIX_MLOCKALL) */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On Windows, WSAEWOULDBLOCK is not always correct: when you see it,
|
* On Windows, WSAEWOULDBLOCK is not always correct: when you see it,
|
||||||
* you need to ask the socket for its actual errno. Also, you need to
|
* you need to ask the socket for its actual errno. Also, you need to
|
||||||
|
@ -137,28 +137,10 @@ MOCK_DECL(const char *, get_uname, (void));
|
|||||||
typedef unsigned long rlim_t;
|
typedef unsigned long rlim_t;
|
||||||
#endif
|
#endif
|
||||||
int set_max_file_descriptors(rlim_t limit, int *max);
|
int set_max_file_descriptors(rlim_t limit, int *max);
|
||||||
int tor_disable_debugger_attach(void);
|
|
||||||
|
|
||||||
#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
|
|
||||||
#define HAVE_LINUX_CAPABILITIES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int have_capability_support(void);
|
|
||||||
|
|
||||||
/** Flag for switch_id; see switch_id() for documentation */
|
|
||||||
#define SWITCH_ID_KEEP_BINDLOW (1<<0)
|
|
||||||
/** Flag for switch_id; see switch_id() for documentation */
|
|
||||||
#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
|
|
||||||
int switch_id(const char *user, unsigned flags);
|
|
||||||
|
|
||||||
char **get_environment(void);
|
|
||||||
|
|
||||||
MOCK_DECL(int, get_total_system_memory, (size_t *mem_out));
|
MOCK_DECL(int, get_total_system_memory, (size_t *mem_out));
|
||||||
|
|
||||||
int compute_num_cpus(void);
|
int compute_num_cpus(void);
|
||||||
|
|
||||||
int tor_mlockall(void);
|
|
||||||
|
|
||||||
/** Macros for MIN/MAX. Never use these when the arguments could have
|
/** Macros for MIN/MAX. Never use these when the arguments could have
|
||||||
* side-effects.
|
* side-effects.
|
||||||
* {With GCC extensions we could probably define a safer MIN/MAX. But
|
* {With GCC extensions we could probably define a safer MIN/MAX. But
|
||||||
|
@ -29,7 +29,6 @@ LIBOR_A_SRC = \
|
|||||||
src/common/compat.c \
|
src/common/compat.c \
|
||||||
src/common/compat_time.c \
|
src/common/compat_time.c \
|
||||||
src/common/util.c \
|
src/common/util.c \
|
||||||
src/common/util_process.c \
|
|
||||||
src/common/token_bucket.c \
|
src/common/token_bucket.c \
|
||||||
src/common/workqueue.c \
|
src/common/workqueue.c \
|
||||||
$(libor_extra_source) \
|
$(libor_extra_source) \
|
||||||
@ -71,7 +70,6 @@ COMMONHEADERS = \
|
|||||||
src/common/timers.h \
|
src/common/timers.h \
|
||||||
src/common/token_bucket.h \
|
src/common/token_bucket.h \
|
||||||
src/common/util.h \
|
src/common/util.h \
|
||||||
src/common/util_process.h \
|
|
||||||
src/common/workqueue.h
|
src/common/workqueue.h
|
||||||
|
|
||||||
noinst_HEADERS+= $(COMMONHEADERS)
|
noinst_HEADERS+= $(COMMONHEADERS)
|
||||||
|
1539
src/common/util.c
1539
src/common/util.c
File diff suppressed because it is too large
Load Diff
@ -91,138 +91,10 @@ int64_t tv_to_msec(const struct timeval *tv);
|
|||||||
((isSock) ? read_all_from_socket((fd), (buf), (count)) \
|
((isSock) ? read_all_from_socket((fd), (buf), (count)) \
|
||||||
: read_all_from_fd((int)(fd), (buf), (count)))
|
: read_all_from_fd((int)(fd), (buf), (count)))
|
||||||
|
|
||||||
/** Status of an I/O stream. */
|
|
||||||
enum stream_status {
|
|
||||||
IO_STREAM_OKAY,
|
|
||||||
IO_STREAM_EAGAIN,
|
|
||||||
IO_STREAM_TERM,
|
|
||||||
IO_STREAM_CLOSED
|
|
||||||
};
|
|
||||||
|
|
||||||
const char *stream_status_to_string(enum stream_status stream_status);
|
|
||||||
|
|
||||||
enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
|
|
||||||
|
|
||||||
/* Process helpers */
|
|
||||||
void start_daemon(void);
|
|
||||||
void finish_daemon(const char *desired_cwd);
|
|
||||||
int write_pidfile(const char *filename);
|
|
||||||
|
|
||||||
void tor_disable_spawning_background_processes(void);
|
|
||||||
|
|
||||||
typedef struct process_handle_t process_handle_t;
|
|
||||||
typedef struct process_environment_t process_environment_t;
|
|
||||||
int tor_spawn_background(const char *const filename, const char **argv,
|
|
||||||
process_environment_t *env,
|
|
||||||
process_handle_t **process_handle_out);
|
|
||||||
|
|
||||||
#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE load_windows_system_library(const TCHAR *library_name);
|
HANDLE load_windows_system_library(const TCHAR *library_name);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int environment_variable_names_equal(const char *s1, const char *s2);
|
|
||||||
|
|
||||||
/* DOCDOC process_environment_t */
|
|
||||||
struct process_environment_t {
|
|
||||||
/** A pointer to a sorted empty-string-terminated sequence of
|
|
||||||
* NUL-terminated strings of the form "NAME=VALUE". */
|
|
||||||
char *windows_environment_block;
|
|
||||||
/** A pointer to a NULL-terminated array of pointers to
|
|
||||||
* NUL-terminated strings of the form "NAME=VALUE". */
|
|
||||||
char **unixoid_environment_block;
|
|
||||||
};
|
|
||||||
|
|
||||||
process_environment_t *process_environment_make(struct smartlist_t *env_vars);
|
|
||||||
void process_environment_free_(process_environment_t *env);
|
|
||||||
#define process_environment_free(env) \
|
|
||||||
FREE_AND_NULL(process_environment_t, process_environment_free_, (env))
|
|
||||||
|
|
||||||
struct smartlist_t *get_current_process_environment_variables(void);
|
|
||||||
|
|
||||||
void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
|
|
||||||
const char *new_var,
|
|
||||||
void (*free_old)(void*),
|
|
||||||
int free_p);
|
|
||||||
|
|
||||||
/* Values of process_handle_t.status. */
|
|
||||||
#define PROCESS_STATUS_NOTRUNNING 0
|
|
||||||
#define PROCESS_STATUS_RUNNING 1
|
|
||||||
#define PROCESS_STATUS_ERROR -1
|
|
||||||
|
|
||||||
#ifdef UTIL_PRIVATE
|
|
||||||
struct waitpid_callback_t;
|
|
||||||
/** Structure to represent the state of a process with which Tor is
|
|
||||||
* communicating. The contents of this structure are private to util.c */
|
|
||||||
struct process_handle_t {
|
|
||||||
/** One of the PROCESS_STATUS_* values */
|
|
||||||
int status;
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE stdin_pipe;
|
|
||||||
HANDLE stdout_pipe;
|
|
||||||
HANDLE stderr_pipe;
|
|
||||||
PROCESS_INFORMATION pid;
|
|
||||||
#else /* !(defined(_WIN32)) */
|
|
||||||
int stdin_pipe;
|
|
||||||
int stdout_pipe;
|
|
||||||
int stderr_pipe;
|
|
||||||
pid_t pid;
|
|
||||||
/** If the process has not given us a SIGCHLD yet, this has the
|
|
||||||
* waitpid_callback_t that gets invoked once it has. Otherwise this
|
|
||||||
* contains NULL. */
|
|
||||||
struct waitpid_callback_t *waitpid_cb;
|
|
||||||
/** The exit status reported by waitpid. */
|
|
||||||
int waitpid_exit_status;
|
|
||||||
#endif /* defined(_WIN32) */
|
|
||||||
};
|
|
||||||
#endif /* defined(UTIL_PRIVATE) */
|
|
||||||
|
|
||||||
/* Return values of tor_get_exit_code() */
|
|
||||||
#define PROCESS_EXIT_RUNNING 1
|
|
||||||
#define PROCESS_EXIT_EXITED 0
|
|
||||||
#define PROCESS_EXIT_ERROR -1
|
|
||||||
int tor_get_exit_code(process_handle_t *process_handle,
|
|
||||||
int block, int *exit_code);
|
|
||||||
int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
|
|
||||||
#ifdef _WIN32
|
|
||||||
ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
|
|
||||||
const process_handle_t *process);
|
|
||||||
#else
|
|
||||||
ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
|
|
||||||
const process_handle_t *process,
|
|
||||||
int *eof);
|
|
||||||
#endif /* defined(_WIN32) */
|
|
||||||
ssize_t tor_read_all_from_process_stdout(
|
|
||||||
const process_handle_t *process_handle, char *buf, size_t count);
|
|
||||||
ssize_t tor_read_all_from_process_stderr(
|
|
||||||
const process_handle_t *process_handle, char *buf, size_t count);
|
|
||||||
char *tor_join_win_cmdline(const char *argv[]);
|
|
||||||
|
|
||||||
int tor_process_get_pid(process_handle_t *process_handle);
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
|
|
||||||
#else
|
|
||||||
int tor_process_get_stdout_pipe(process_handle_t *process_handle);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
MOCK_DECL(struct smartlist_t *,
|
|
||||||
tor_get_lines_from_handle,(HANDLE *handle,
|
|
||||||
enum stream_status *stream_status));
|
|
||||||
#else
|
|
||||||
MOCK_DECL(struct smartlist_t *,
|
|
||||||
tor_get_lines_from_handle,(int fd,
|
|
||||||
enum stream_status *stream_status));
|
|
||||||
#endif /* defined(_WIN32) */
|
|
||||||
|
|
||||||
int
|
|
||||||
tor_terminate_process(process_handle_t *process_handle);
|
|
||||||
|
|
||||||
MOCK_DECL(void,
|
|
||||||
tor_process_handle_destroy,(process_handle_t *process_handle,
|
|
||||||
int also_terminate_process));
|
|
||||||
|
|
||||||
/* ===== Insecure rng */
|
/* ===== Insecure rng */
|
||||||
typedef struct tor_weak_rng_t {
|
typedef struct tor_weak_rng_t {
|
||||||
uint32_t state;
|
uint32_t state;
|
||||||
@ -237,19 +109,4 @@ int32_t tor_weak_random_range(tor_weak_rng_t *rng, int32_t top);
|
|||||||
* <b>n</b> */
|
* <b>n</b> */
|
||||||
#define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n)))
|
#define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n)))
|
||||||
|
|
||||||
#ifdef UTIL_PRIVATE
|
|
||||||
/* Prototypes for private functions only used by util.c (and unit tests) */
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
STATIC int format_helper_exit_status(unsigned char child_state,
|
|
||||||
int saved_errno, char *hex_errno);
|
|
||||||
|
|
||||||
/* Space for hex values of child state, a slash, saved_errno (with
|
|
||||||
leading minus) and newline (no null) */
|
|
||||||
#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
|
|
||||||
1 + sizeof(int) * 2 + 1)
|
|
||||||
#endif /* !defined(_WIN32) */
|
|
||||||
|
|
||||||
#endif /* defined(UTIL_PRIVATE) */
|
|
||||||
|
|
||||||
#endif /* !defined(TOR_UTIL_H) */
|
#endif /* !defined(TOR_UTIL_H) */
|
||||||
|
@ -17,6 +17,7 @@ include src/lib/log/include.am
|
|||||||
include src/lib/memarea/include.am
|
include src/lib/memarea/include.am
|
||||||
include src/lib/malloc/include.am
|
include src/lib/malloc/include.am
|
||||||
include src/lib/net/include.am
|
include src/lib/net/include.am
|
||||||
|
include src/lib/process/include.am
|
||||||
include src/lib/sandbox/include.am
|
include src/lib/sandbox/include.am
|
||||||
include src/lib/string/include.am
|
include src/lib/string/include.am
|
||||||
include src/lib/smartlist_core/include.am
|
include src/lib/smartlist_core/include.am
|
||||||
|
15
src/lib/process/.may_include
Normal file
15
src/lib/process/.may_include
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
orconfig.h
|
||||||
|
|
||||||
|
lib/cc/*.h
|
||||||
|
lib/container/*.h
|
||||||
|
lib/ctime/*.h
|
||||||
|
lib/err/*.h
|
||||||
|
lib/fs/*.h
|
||||||
|
lib/log/*.h
|
||||||
|
lib/malloc/*.h
|
||||||
|
lib/process/*.h
|
||||||
|
lib/string/*.h
|
||||||
|
lib/testsupport/*.h
|
||||||
|
lib/thread/*.h
|
||||||
|
|
||||||
|
ht.h
|
159
src/lib/process/daemon.c
Normal file
159
src/lib/process/daemon.c
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/* Copyright (c) 2003, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#include "orconfig.h"
|
||||||
|
#include "lib/process/daemon.h"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
|
#include "lib/fs/files.h"
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
#include "lib/thread/threads.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_FCNTL_H
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Based on code contributed by christian grothoff */
|
||||||
|
/** True iff we've called start_daemon(). */
|
||||||
|
static int start_daemon_called = 0;
|
||||||
|
/** True iff we've called finish_daemon(). */
|
||||||
|
static int finish_daemon_called = 0;
|
||||||
|
/** Socketpair used to communicate between parent and child process while
|
||||||
|
* daemonizing. */
|
||||||
|
static int daemon_filedes[2];
|
||||||
|
/** Start putting the process into daemon mode: fork and drop all resources
|
||||||
|
* except standard fds. The parent process never returns, but stays around
|
||||||
|
* until finish_daemon is called. (Note: it's safe to call this more
|
||||||
|
* than once: calls after the first are ignored.)
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
start_daemon(void)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
if (start_daemon_called)
|
||||||
|
return;
|
||||||
|
start_daemon_called = 1;
|
||||||
|
|
||||||
|
if (pipe(daemon_filedes)) {
|
||||||
|
/* LCOV_EXCL_START */
|
||||||
|
log_err(LD_GENERAL,"pipe failed; exiting. Error was %s", strerror(errno));
|
||||||
|
exit(1); // exit ok: during daemonize, pipe failed.
|
||||||
|
/* LCOV_EXCL_STOP */
|
||||||
|
}
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
/* LCOV_EXCL_START */
|
||||||
|
log_err(LD_GENERAL,"fork failed. Exiting.");
|
||||||
|
exit(1); // exit ok: during daemonize, fork failed
|
||||||
|
/* LCOV_EXCL_STOP */
|
||||||
|
}
|
||||||
|
if (pid) { /* Parent */
|
||||||
|
int ok;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
close(daemon_filedes[1]); /* we only read */
|
||||||
|
ok = -1;
|
||||||
|
while (0 < read(daemon_filedes[0], &c, sizeof(char))) {
|
||||||
|
if (c == '.')
|
||||||
|
ok = 1;
|
||||||
|
}
|
||||||
|
fflush(stdout);
|
||||||
|
if (ok == 1)
|
||||||
|
exit(0); // exit ok: during daemonize, daemonizing.
|
||||||
|
else
|
||||||
|
exit(1); /* child reported error. exit ok: daemonize failed. */
|
||||||
|
} else { /* Child */
|
||||||
|
close(daemon_filedes[0]); /* we only write */
|
||||||
|
|
||||||
|
(void) setsid(); /* Detach from controlling terminal */
|
||||||
|
/*
|
||||||
|
* Fork one more time, so the parent (the session group leader) can exit.
|
||||||
|
* This means that we, as a non-session group leader, can never regain a
|
||||||
|
* controlling terminal. This part is recommended by Stevens's
|
||||||
|
* _Advanced Programming in the Unix Environment_.
|
||||||
|
*/
|
||||||
|
if (fork() != 0) {
|
||||||
|
exit(0); // exit ok: during daemonize, fork failed (2)
|
||||||
|
}
|
||||||
|
set_main_thread(); /* We are now the main thread. */
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finish putting the process into daemon mode: drop standard fds, and tell
|
||||||
|
* the parent process to exit. (Note: it's safe to call this more than once:
|
||||||
|
* calls after the first are ignored. Calls start_daemon first if it hasn't
|
||||||
|
* been called already.)
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
finish_daemon(const char *desired_cwd)
|
||||||
|
{
|
||||||
|
int nullfd;
|
||||||
|
char c = '.';
|
||||||
|
if (finish_daemon_called)
|
||||||
|
return;
|
||||||
|
if (!start_daemon_called)
|
||||||
|
start_daemon();
|
||||||
|
finish_daemon_called = 1;
|
||||||
|
|
||||||
|
if (!desired_cwd)
|
||||||
|
desired_cwd = "/";
|
||||||
|
/* Don't hold the wrong FS mounted */
|
||||||
|
if (chdir(desired_cwd) < 0) {
|
||||||
|
log_err(LD_GENERAL,"chdir to \"%s\" failed. Exiting.",desired_cwd);
|
||||||
|
exit(1); // exit ok: during daemonize, chdir failed.
|
||||||
|
}
|
||||||
|
|
||||||
|
nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0);
|
||||||
|
if (nullfd < 0) {
|
||||||
|
/* LCOV_EXCL_START */
|
||||||
|
log_err(LD_GENERAL,"/dev/null can't be opened. Exiting.");
|
||||||
|
exit(1); // exit ok: during daemonize, couldn't open /dev/null
|
||||||
|
/* LCOV_EXCL_STOP */
|
||||||
|
}
|
||||||
|
/* close fds linking to invoking terminal, but
|
||||||
|
* close usual incoming fds, but redirect them somewhere
|
||||||
|
* useful so the fds don't get reallocated elsewhere.
|
||||||
|
*/
|
||||||
|
if (dup2(nullfd,0) < 0 ||
|
||||||
|
dup2(nullfd,1) < 0 ||
|
||||||
|
dup2(nullfd,2) < 0) {
|
||||||
|
/* LCOV_EXCL_START */
|
||||||
|
log_err(LD_GENERAL,"dup2 failed. Exiting.");
|
||||||
|
exit(1); // exit ok: during daemonize, dup2 failed.
|
||||||
|
/* LCOV_EXCL_STOP */
|
||||||
|
}
|
||||||
|
if (nullfd > 2)
|
||||||
|
close(nullfd);
|
||||||
|
/* signal success */
|
||||||
|
if (write(daemon_filedes[1], &c, sizeof(char)) != sizeof(char)) {
|
||||||
|
log_err(LD_GENERAL,"write failed. Exiting.");
|
||||||
|
}
|
||||||
|
close(daemon_filedes[1]);
|
||||||
|
}
|
||||||
|
#else /* !(!defined(_WIN32)) */
|
||||||
|
/* defined(_WIN32) */
|
||||||
|
void
|
||||||
|
start_daemon(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void
|
||||||
|
finish_daemon(const char *cp)
|
||||||
|
{
|
||||||
|
(void)cp;
|
||||||
|
}
|
||||||
|
#endif /* !defined(_WIN32) */
|
12
src/lib/process/daemon.h
Normal file
12
src/lib/process/daemon.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#ifndef TOR_DAEMON_H
|
||||||
|
#define TOR_DAEMON_H
|
||||||
|
|
||||||
|
void start_daemon(void);
|
||||||
|
void finish_daemon(const char *desired_cwd);
|
||||||
|
|
||||||
|
#endif
|
215
src/lib/process/env.c
Normal file
215
src/lib/process/env.c
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#include "orconfig.h"
|
||||||
|
#include "lib/process/env.h"
|
||||||
|
|
||||||
|
#include "lib/malloc/util_malloc.h"
|
||||||
|
#include "lib/ctime/di_ops.h"
|
||||||
|
#include "lib/container/smartlist.h"
|
||||||
|
#include "lib/log/util_bug.h"
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
#include "lib/malloc/util_malloc.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef HAVE__NSGETENVIRON
|
||||||
|
#ifndef HAVE_EXTERN_ENVIRON_DECLARED
|
||||||
|
/* Some platforms declare environ under some circumstances, others don't. */
|
||||||
|
#ifndef RUNNING_DOXYGEN
|
||||||
|
extern char **environ;
|
||||||
|
#endif
|
||||||
|
#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */
|
||||||
|
#endif /* !defined(HAVE__NSGETENVIRON) */
|
||||||
|
|
||||||
|
/** Return the current environment. This is a portable replacement for
|
||||||
|
* 'environ'. */
|
||||||
|
char **
|
||||||
|
get_environment(void)
|
||||||
|
{
|
||||||
|
#ifdef HAVE__NSGETENVIRON
|
||||||
|
/* This is for compatibility between OSX versions. Otherwise (for example)
|
||||||
|
* when we do a mostly-static build on OSX 10.7, the resulting binary won't
|
||||||
|
* work on OSX 10.6. */
|
||||||
|
return *_NSGetEnviron();
|
||||||
|
#else /* !(defined(HAVE__NSGETENVIRON)) */
|
||||||
|
return environ;
|
||||||
|
#endif /* defined(HAVE__NSGETENVIRON) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper: return the number of characters in <b>s</b> preceding the first
|
||||||
|
* occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return
|
||||||
|
* the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */
|
||||||
|
static inline size_t
|
||||||
|
str_num_before(const char *s, char ch)
|
||||||
|
{
|
||||||
|
const char *cp = strchr(s, ch);
|
||||||
|
if (cp)
|
||||||
|
return cp - s;
|
||||||
|
else
|
||||||
|
return strlen(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b>
|
||||||
|
* to have the same name as strings in a process's environment. */
|
||||||
|
int
|
||||||
|
environment_variable_names_equal(const char *s1, const char *s2)
|
||||||
|
{
|
||||||
|
size_t s1_name_len = str_num_before(s1, '=');
|
||||||
|
size_t s2_name_len = str_num_before(s2, '=');
|
||||||
|
|
||||||
|
return (s1_name_len == s2_name_len &&
|
||||||
|
tor_memeq(s1, s2, s1_name_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Free <b>env</b> (assuming it was produced by
|
||||||
|
* process_environment_make). */
|
||||||
|
void
|
||||||
|
process_environment_free_(process_environment_t *env)
|
||||||
|
{
|
||||||
|
if (env == NULL) return;
|
||||||
|
|
||||||
|
/* As both an optimization hack to reduce consing on Unixoid systems
|
||||||
|
* and a nice way to ensure that some otherwise-Windows-specific
|
||||||
|
* code will always get tested before changes to it get merged, the
|
||||||
|
* strings which env->unixoid_environment_block points to are packed
|
||||||
|
* into env->windows_environment_block. */
|
||||||
|
tor_free(env->unixoid_environment_block);
|
||||||
|
tor_free(env->windows_environment_block);
|
||||||
|
|
||||||
|
tor_free(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Make a process_environment_t containing the environment variables
|
||||||
|
* specified in <b>env_vars</b> (as C strings of the form
|
||||||
|
* "NAME=VALUE"). */
|
||||||
|
process_environment_t *
|
||||||
|
process_environment_make(struct smartlist_t *env_vars)
|
||||||
|
{
|
||||||
|
process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
|
||||||
|
int n_env_vars = smartlist_len(env_vars);
|
||||||
|
int i;
|
||||||
|
size_t total_env_length;
|
||||||
|
smartlist_t *env_vars_sorted;
|
||||||
|
|
||||||
|
tor_assert(n_env_vars + 1 != 0);
|
||||||
|
env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *));
|
||||||
|
/* env->unixoid_environment_block is already NULL-terminated,
|
||||||
|
* because we assume that NULL == 0 (and check that during compilation). */
|
||||||
|
|
||||||
|
total_env_length = 1; /* terminating NUL of terminating empty string */
|
||||||
|
for (i = 0; i < n_env_vars; ++i) {
|
||||||
|
const char *s = smartlist_get(env_vars, (int)i);
|
||||||
|
size_t slen = strlen(s);
|
||||||
|
|
||||||
|
tor_assert(slen + 1 != 0);
|
||||||
|
tor_assert(slen + 1 < SIZE_MAX - total_env_length);
|
||||||
|
total_env_length += slen + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
env->windows_environment_block = tor_malloc_zero(total_env_length);
|
||||||
|
/* env->windows_environment_block is already
|
||||||
|
* (NUL-terminated-empty-string)-terminated. */
|
||||||
|
|
||||||
|
/* Some versions of Windows supposedly require that environment
|
||||||
|
* blocks be sorted. Or maybe some Windows programs (or their
|
||||||
|
* runtime libraries) fail to look up strings in non-sorted
|
||||||
|
* environment blocks.
|
||||||
|
*
|
||||||
|
* Also, sorting strings makes it easy to find duplicate environment
|
||||||
|
* variables and environment-variable strings without an '=' on all
|
||||||
|
* OSes, and they can cause badness. Let's complain about those. */
|
||||||
|
env_vars_sorted = smartlist_new();
|
||||||
|
smartlist_add_all(env_vars_sorted, env_vars);
|
||||||
|
smartlist_sort_strings(env_vars_sorted);
|
||||||
|
|
||||||
|
/* Now copy the strings into the environment blocks. */
|
||||||
|
{
|
||||||
|
char *cp = env->windows_environment_block;
|
||||||
|
const char *prev_env_var = NULL;
|
||||||
|
|
||||||
|
for (i = 0; i < n_env_vars; ++i) {
|
||||||
|
const char *s = smartlist_get(env_vars_sorted, (int)i);
|
||||||
|
size_t slen = strlen(s);
|
||||||
|
size_t s_name_len = str_num_before(s, '=');
|
||||||
|
|
||||||
|
if (s_name_len == slen) {
|
||||||
|
log_warn(LD_GENERAL,
|
||||||
|
"Preparing an environment containing a variable "
|
||||||
|
"without a value: %s",
|
||||||
|
s);
|
||||||
|
}
|
||||||
|
if (prev_env_var != NULL &&
|
||||||
|
environment_variable_names_equal(s, prev_env_var)) {
|
||||||
|
log_warn(LD_GENERAL,
|
||||||
|
"Preparing an environment containing two variables "
|
||||||
|
"with the same name: %s and %s",
|
||||||
|
prev_env_var, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_env_var = s;
|
||||||
|
|
||||||
|
/* Actually copy the string into the environment. */
|
||||||
|
memcpy(cp, s, slen+1);
|
||||||
|
env->unixoid_environment_block[i] = cp;
|
||||||
|
cp += slen+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tor_assert(cp == env->windows_environment_block + total_env_length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
smartlist_free(env_vars_sorted);
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return a newly allocated smartlist containing every variable in
|
||||||
|
* this process's environment, as a NUL-terminated string of the form
|
||||||
|
* "NAME=VALUE". Note that on some/many/most/all OSes, the parent
|
||||||
|
* process can put strings not of that form in our environment;
|
||||||
|
* callers should try to not get crashed by that.
|
||||||
|
*
|
||||||
|
* The returned strings are heap-allocated, and must be freed by the
|
||||||
|
* caller. */
|
||||||
|
struct smartlist_t *
|
||||||
|
get_current_process_environment_variables(void)
|
||||||
|
{
|
||||||
|
smartlist_t *sl = smartlist_new();
|
||||||
|
|
||||||
|
char **environ_tmp; /* Not const char ** ? Really? */
|
||||||
|
for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) {
|
||||||
|
smartlist_add_strdup(sl, *environ_tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For each string s in <b>env_vars</b> such that
|
||||||
|
* environment_variable_names_equal(s, <b>new_var</b>), remove it; if
|
||||||
|
* <b>free_p</b> is non-zero, call <b>free_old</b>(s). If
|
||||||
|
* <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */
|
||||||
|
void
|
||||||
|
set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
|
||||||
|
const char *new_var,
|
||||||
|
void (*free_old)(void*),
|
||||||
|
int free_p)
|
||||||
|
{
|
||||||
|
SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
|
||||||
|
if (environment_variable_names_equal(s, new_var)) {
|
||||||
|
SMARTLIST_DEL_CURRENT(env_vars, s);
|
||||||
|
if (free_p) {
|
||||||
|
free_old((void *)s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} SMARTLIST_FOREACH_END(s);
|
||||||
|
|
||||||
|
if (strchr(new_var, '=') != NULL) {
|
||||||
|
smartlist_add(env_vars, (void *)new_var);
|
||||||
|
}
|
||||||
|
}
|
36
src/lib/process/env.h
Normal file
36
src/lib/process/env.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#ifndef TOR_ENV_H
|
||||||
|
#define TOR_ENV_H
|
||||||
|
|
||||||
|
char **get_environment(void);
|
||||||
|
|
||||||
|
struct smartlist_t;
|
||||||
|
|
||||||
|
int environment_variable_names_equal(const char *s1, const char *s2);
|
||||||
|
|
||||||
|
/* DOCDOC process_environment_t */
|
||||||
|
typedef struct process_environment_t {
|
||||||
|
/** A pointer to a sorted empty-string-terminated sequence of
|
||||||
|
* NUL-terminated strings of the form "NAME=VALUE". */
|
||||||
|
char *windows_environment_block;
|
||||||
|
/** A pointer to a NULL-terminated array of pointers to
|
||||||
|
* NUL-terminated strings of the form "NAME=VALUE". */
|
||||||
|
char **unixoid_environment_block;
|
||||||
|
} process_environment_t;
|
||||||
|
|
||||||
|
process_environment_t *process_environment_make(struct smartlist_t *env_vars);
|
||||||
|
void process_environment_free_(process_environment_t *env);
|
||||||
|
#define process_environment_free(env) \
|
||||||
|
FREE_AND_NULL(process_environment_t, process_environment_free_, (env))
|
||||||
|
|
||||||
|
struct smartlist_t *get_current_process_environment_variables(void);
|
||||||
|
|
||||||
|
void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
|
||||||
|
const char *new_var,
|
||||||
|
void (*free_old)(void*),
|
||||||
|
int free_p);
|
||||||
|
#endif
|
29
src/lib/process/include.am
Normal file
29
src/lib/process/include.am
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
noinst_LIBRARIES += src/lib/libtor-process.a
|
||||||
|
|
||||||
|
if UNITTESTS_ENABLED
|
||||||
|
noinst_LIBRARIES += src/lib/libtor-process-testing.a
|
||||||
|
endif
|
||||||
|
|
||||||
|
src_lib_libtor_process_a_SOURCES = \
|
||||||
|
src/lib/process/daemon.c \
|
||||||
|
src/lib/process/env.c \
|
||||||
|
src/lib/process/pidfile.c \
|
||||||
|
src/lib/process/restrict.c \
|
||||||
|
src/lib/process/setuid.c \
|
||||||
|
src/lib/process/subprocess.c \
|
||||||
|
src/lib/process/waitpid.c
|
||||||
|
|
||||||
|
src_lib_libtor_process_testing_a_SOURCES = \
|
||||||
|
$(src_lib_libtor_process_a_SOURCES)
|
||||||
|
src_lib_libtor_process_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
|
||||||
|
src_lib_libtor_process_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
|
||||||
|
|
||||||
|
noinst_HEADERS += \
|
||||||
|
src/lib/process/daemon.h \
|
||||||
|
src/lib/process/env.h \
|
||||||
|
src/lib/process/pidfile.h \
|
||||||
|
src/lib/process/restrict.h \
|
||||||
|
src/lib/process/setuid.h \
|
||||||
|
src/lib/process/subprocess.h \
|
||||||
|
src/lib/process/waitpid.h
|
47
src/lib/process/pidfile.c
Normal file
47
src/lib/process/pidfile.c
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#include "orconfig.h"
|
||||||
|
#include "lib/process/pidfile.h"
|
||||||
|
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/** Write the current process ID, followed by NL, into <b>filename</b>.
|
||||||
|
* Return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
write_pidfile(const char *filename)
|
||||||
|
{
|
||||||
|
FILE *pidfile;
|
||||||
|
|
||||||
|
if ((pidfile = fopen(filename, "w")) == NULL) {
|
||||||
|
log_warn(LD_FS, "Unable to open \"%s\" for writing: %s", filename,
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
#ifdef _WIN32
|
||||||
|
int pid = (int)_getpid();
|
||||||
|
#else
|
||||||
|
int pid = (int)getpid();
|
||||||
|
#endif
|
||||||
|
int rv = 0;
|
||||||
|
if (fprintf(pidfile, "%d\n", pid) < 0)
|
||||||
|
rv = -1;
|
||||||
|
if (fclose(pidfile) < 0)
|
||||||
|
rv = -1;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
11
src/lib/process/pidfile.h
Normal file
11
src/lib/process/pidfile.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#ifndef TOR_PIDFILE_H
|
||||||
|
#define TOR_PIDFILE_H
|
||||||
|
|
||||||
|
int write_pidfile(const char *filename);
|
||||||
|
|
||||||
|
#endif
|
144
src/lib/process/restrict.c
Normal file
144
src/lib/process/restrict.c
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#include "orconfig.h"
|
||||||
|
#include "lib/process/restrict.h"
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
|
||||||
|
/* We only use the linux prctl for now. There is no Win32 support; this may
|
||||||
|
* also work on various BSD systems and Mac OS X - send testing feedback!
|
||||||
|
*
|
||||||
|
* On recent Gnu/Linux kernels it is possible to create a system-wide policy
|
||||||
|
* that will prevent non-root processes from attaching to other processes
|
||||||
|
* unless they are the parent process; thus gdb can attach to programs that
|
||||||
|
* they execute but they cannot attach to other processes running as the same
|
||||||
|
* user. The system wide policy may be set with the sysctl
|
||||||
|
* kernel.yama.ptrace_scope or by inspecting
|
||||||
|
* /proc/sys/kernel/yama/ptrace_scope and it is 1 by default on Ubuntu 11.04.
|
||||||
|
*
|
||||||
|
* This ptrace scope will be ignored on Gnu/Linux for users with
|
||||||
|
* CAP_SYS_PTRACE and so it is very likely that root will still be able to
|
||||||
|
* attach to the Tor process.
|
||||||
|
*/
|
||||||
|
/** Attempt to disable debugger attachment: return 1 on success, -1 on
|
||||||
|
* failure, and 0 if we don't know how to try on this platform. */
|
||||||
|
int
|
||||||
|
tor_disable_debugger_attach(void)
|
||||||
|
{
|
||||||
|
int r = -1;
|
||||||
|
log_debug(LD_CONFIG,
|
||||||
|
"Attemping to disable debugger attachment to Tor for "
|
||||||
|
"unprivileged users.");
|
||||||
|
#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \
|
||||||
|
&& defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
|
||||||
|
#define TRIED_TO_DISABLE
|
||||||
|
r = prctl(PR_SET_DUMPABLE, 0);
|
||||||
|
#elif defined(__APPLE__) && defined(PT_DENY_ATTACH)
|
||||||
|
#define TRIED_TO_ATTACH
|
||||||
|
r = ptrace(PT_DENY_ATTACH, 0, 0, 0);
|
||||||
|
#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */
|
||||||
|
|
||||||
|
// XXX: TODO - Mac OS X has dtrace and this may be disabled.
|
||||||
|
// XXX: TODO - Windows probably has something similar
|
||||||
|
#ifdef TRIED_TO_DISABLE
|
||||||
|
if (r == 0) {
|
||||||
|
log_debug(LD_CONFIG,"Debugger attachment disabled for "
|
||||||
|
"unprivileged users.");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
#endif /* defined(TRIED_TO_DISABLE) */
|
||||||
|
#undef TRIED_TO_DISABLE
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
|
||||||
|
#define HAVE_UNIX_MLOCKALL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_UNIX_MLOCKALL
|
||||||
|
/** Attempt to raise the current and max rlimit to infinity for our process.
|
||||||
|
* This only needs to be done once and can probably only be done when we have
|
||||||
|
* not already dropped privileges.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tor_set_max_memlock(void)
|
||||||
|
{
|
||||||
|
/* Future consideration for Windows is probably SetProcessWorkingSetSize
|
||||||
|
* This is similar to setting the memory rlimit of RLIMIT_MEMLOCK
|
||||||
|
* http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct rlimit limit;
|
||||||
|
|
||||||
|
/* RLIM_INFINITY is -1 on some platforms. */
|
||||||
|
limit.rlim_cur = RLIM_INFINITY;
|
||||||
|
limit.rlim_max = RLIM_INFINITY;
|
||||||
|
|
||||||
|
if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) {
|
||||||
|
if (errno == EPERM) {
|
||||||
|
log_warn(LD_GENERAL, "You appear to lack permissions to change memory "
|
||||||
|
"limits. Are you root?");
|
||||||
|
}
|
||||||
|
log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s",
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* defined(HAVE_UNIX_MLOCKALL) */
|
||||||
|
|
||||||
|
/** Attempt to lock all current and all future memory pages.
|
||||||
|
* This should only be called once and while we're privileged.
|
||||||
|
* Like mlockall() we return 0 when we're successful and -1 when we're not.
|
||||||
|
* Unlike mlockall() we return 1 if we've already attempted to lock memory.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tor_mlockall(void)
|
||||||
|
{
|
||||||
|
static int memory_lock_attempted = 0;
|
||||||
|
|
||||||
|
if (memory_lock_attempted) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_lock_attempted = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Future consideration for Windows may be VirtualLock
|
||||||
|
* VirtualLock appears to implement mlock() but not mlockall()
|
||||||
|
*
|
||||||
|
* http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_UNIX_MLOCKALL
|
||||||
|
if (tor_set_max_memlock() == 0) {
|
||||||
|
log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) {
|
||||||
|
log_info(LD_GENERAL, "Insecure OS paging is effectively disabled.");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if (errno == ENOSYS) {
|
||||||
|
/* Apple - it's 2009! I'm looking at you. Grrr. */
|
||||||
|
log_notice(LD_GENERAL, "It appears that mlockall() is not available on "
|
||||||
|
"your platform.");
|
||||||
|
} else if (errno == EPERM) {
|
||||||
|
log_notice(LD_GENERAL, "It appears that you lack the permissions to "
|
||||||
|
"lock memory. Are you root?");
|
||||||
|
}
|
||||||
|
log_notice(LD_GENERAL, "Unable to lock all current and future memory "
|
||||||
|
"pages: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else /* !(defined(HAVE_UNIX_MLOCKALL)) */
|
||||||
|
log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?");
|
||||||
|
return -1;
|
||||||
|
#endif /* defined(HAVE_UNIX_MLOCKALL) */
|
||||||
|
}
|
17
src/lib/process/restrict.h
Normal file
17
src/lib/process/restrict.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \file waitpid.h
|
||||||
|
* \brief Headers for waitpid.c
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifndef TOR_RESTRICT_H
|
||||||
|
#define TOR_RESTRICT_H
|
||||||
|
|
||||||
|
int tor_disable_debugger_attach(void);
|
||||||
|
int tor_mlockall(void);
|
||||||
|
|
||||||
|
#endif /* !defined(TOR_RESTRICT_H) */
|
375
src/lib/process/setuid.c
Normal file
375
src/lib/process/setuid.c
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
/* Copyright (c) 2003, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#include "orconfig.h"
|
||||||
|
#include "lib/process/setuid.h"
|
||||||
|
|
||||||
|
#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
|
||||||
|
#define HAVE_LINUX_CAPABILITIES
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lib/container/smartlist.h"
|
||||||
|
#include "lib/fs/userdb.h"
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
#include "lib/log/util_bug.h"
|
||||||
|
#include "lib/malloc/util_malloc.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_GRP_H
|
||||||
|
#include <grp.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_PWD_H
|
||||||
|
#include <pwd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/** Log details of current user and group credentials. Return 0 on
|
||||||
|
* success. Logs and return -1 on failure.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
log_credential_status(void)
|
||||||
|
{
|
||||||
|
/** Log level to use when describing non-error UID/GID status. */
|
||||||
|
#define CREDENTIAL_LOG_LEVEL LOG_INFO
|
||||||
|
/* Real, effective and saved UIDs */
|
||||||
|
uid_t ruid, euid, suid;
|
||||||
|
/* Read, effective and saved GIDs */
|
||||||
|
gid_t rgid, egid, sgid;
|
||||||
|
/* Supplementary groups */
|
||||||
|
gid_t *sup_gids = NULL;
|
||||||
|
int sup_gids_size;
|
||||||
|
/* Number of supplementary groups */
|
||||||
|
int ngids;
|
||||||
|
|
||||||
|
/* log UIDs */
|
||||||
|
#ifdef HAVE_GETRESUID
|
||||||
|
if (getresuid(&ruid, &euid, &suid) != 0 ) {
|
||||||
|
log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
||||||
|
"UID is %u (real), %u (effective), %u (saved)",
|
||||||
|
(unsigned)ruid, (unsigned)euid, (unsigned)suid);
|
||||||
|
}
|
||||||
|
#else /* !(defined(HAVE_GETRESUID)) */
|
||||||
|
/* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
|
||||||
|
ruid = getuid();
|
||||||
|
euid = geteuid();
|
||||||
|
(void)suid;
|
||||||
|
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
||||||
|
"UID is %u (real), %u (effective), unknown (saved)",
|
||||||
|
(unsigned)ruid, (unsigned)euid);
|
||||||
|
#endif /* defined(HAVE_GETRESUID) */
|
||||||
|
|
||||||
|
/* log GIDs */
|
||||||
|
#ifdef HAVE_GETRESGID
|
||||||
|
if (getresgid(&rgid, &egid, &sgid) != 0 ) {
|
||||||
|
log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
||||||
|
"GID is %u (real), %u (effective), %u (saved)",
|
||||||
|
(unsigned)rgid, (unsigned)egid, (unsigned)sgid);
|
||||||
|
}
|
||||||
|
#else /* !(defined(HAVE_GETRESGID)) */
|
||||||
|
/* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
|
||||||
|
rgid = getgid();
|
||||||
|
egid = getegid();
|
||||||
|
(void)sgid;
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
|
||||||
|
"GID is %u (real), %u (effective), unknown (saved)",
|
||||||
|
(unsigned)rgid, (unsigned)egid);
|
||||||
|
#endif /* defined(HAVE_GETRESGID) */
|
||||||
|
|
||||||
|
/* log supplementary groups */
|
||||||
|
sup_gids_size = 64;
|
||||||
|
sup_gids = tor_calloc(64, sizeof(gid_t));
|
||||||
|
while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
|
||||||
|
errno == EINVAL &&
|
||||||
|
sup_gids_size < NGROUPS_MAX) {
|
||||||
|
sup_gids_size *= 2;
|
||||||
|
sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngids < 0) {
|
||||||
|
log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
|
||||||
|
strerror(errno));
|
||||||
|
tor_free(sup_gids);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
int i, retval = 0;
|
||||||
|
char *s = NULL;
|
||||||
|
smartlist_t *elts = smartlist_new();
|
||||||
|
|
||||||
|
for (i = 0; i<ngids; i++) {
|
||||||
|
smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
s = smartlist_join_strings(elts, " ", 0, NULL);
|
||||||
|
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
|
||||||
|
|
||||||
|
tor_free(s);
|
||||||
|
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
|
||||||
|
smartlist_free(elts);
|
||||||
|
tor_free(sup_gids);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* !defined(_WIN32) */
|
||||||
|
|
||||||
|
/** Return true iff we were compiled with capability support, and capabilities
|
||||||
|
* seem to work. **/
|
||||||
|
int
|
||||||
|
have_capability_support(void)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_LINUX_CAPABILITIES
|
||||||
|
cap_t caps = cap_get_proc();
|
||||||
|
if (caps == NULL)
|
||||||
|
return 0;
|
||||||
|
cap_free(caps);
|
||||||
|
return 1;
|
||||||
|
#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
|
||||||
|
return 0;
|
||||||
|
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LINUX_CAPABILITIES
|
||||||
|
/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
|
||||||
|
* appropriate.
|
||||||
|
*
|
||||||
|
* If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
|
||||||
|
* CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
|
||||||
|
* setuid().
|
||||||
|
*
|
||||||
|
* If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
|
||||||
|
* PR_KEEPCAPS.
|
||||||
|
*
|
||||||
|
* Return 0 on success, and -1 on failure.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
drop_capabilities(int pre_setuid)
|
||||||
|
{
|
||||||
|
/* We keep these three capabilities, and these only, as we setuid.
|
||||||
|
* After we setuid, we drop all but the first. */
|
||||||
|
const cap_value_t caplist[] = {
|
||||||
|
CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
|
||||||
|
};
|
||||||
|
const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
|
||||||
|
const int n_effective = pre_setuid ? 3 : 1;
|
||||||
|
const int n_permitted = pre_setuid ? 3 : 1;
|
||||||
|
const int n_inheritable = 1;
|
||||||
|
const int keepcaps = pre_setuid ? 1 : 0;
|
||||||
|
|
||||||
|
/* Sets whether we keep capabilities across a setuid. */
|
||||||
|
if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
|
||||||
|
log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
|
||||||
|
where, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cap_t caps = cap_get_proc();
|
||||||
|
if (!caps) {
|
||||||
|
log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
|
||||||
|
where, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cap_clear(caps);
|
||||||
|
|
||||||
|
cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
|
||||||
|
cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
|
||||||
|
cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
|
||||||
|
|
||||||
|
int r = cap_set_proc(caps);
|
||||||
|
cap_free(caps);
|
||||||
|
if (r < 0) {
|
||||||
|
log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
|
||||||
|
where, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
||||||
|
|
||||||
|
/** Call setuid and setgid to run as <b>user</b> and switch to their
|
||||||
|
* primary group. Return 0 on success. On failure, log and return -1.
|
||||||
|
*
|
||||||
|
* If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
|
||||||
|
* system to retain the abilitity to bind low ports.
|
||||||
|
*
|
||||||
|
* If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
|
||||||
|
* don't have capability support.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
switch_id(const char *user, const unsigned flags)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
const struct passwd *pw = NULL;
|
||||||
|
uid_t old_uid;
|
||||||
|
gid_t old_gid;
|
||||||
|
static int have_already_switched_id = 0;
|
||||||
|
const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
|
||||||
|
const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
|
||||||
|
|
||||||
|
tor_assert(user);
|
||||||
|
|
||||||
|
if (have_already_switched_id)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Log the initial credential state */
|
||||||
|
if (log_credential_status())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
|
||||||
|
|
||||||
|
/* Get old UID/GID to check if we changed correctly */
|
||||||
|
old_uid = getuid();
|
||||||
|
old_gid = getgid();
|
||||||
|
|
||||||
|
/* Lookup the user and group information, if we have a problem, bail out. */
|
||||||
|
pw = tor_getpwnam(user);
|
||||||
|
if (pw == NULL) {
|
||||||
|
log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LINUX_CAPABILITIES
|
||||||
|
(void) warn_if_no_caps;
|
||||||
|
if (keep_bindlow) {
|
||||||
|
if (drop_capabilities(1))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
|
||||||
|
(void) keep_bindlow;
|
||||||
|
if (warn_if_no_caps) {
|
||||||
|
log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
|
||||||
|
"on this system.");
|
||||||
|
}
|
||||||
|
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
||||||
|
|
||||||
|
/* Properly switch egid,gid,euid,uid here or bail out */
|
||||||
|
if (setgroups(1, &pw->pw_gid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
|
||||||
|
(int)pw->pw_gid, strerror(errno));
|
||||||
|
if (old_uid == pw->pw_uid) {
|
||||||
|
log_warn(LD_GENERAL, "Tor is already running as %s. You do not need "
|
||||||
|
"the \"User\" option if you are already running as the user "
|
||||||
|
"you want to be. (If you did not set the User option in your "
|
||||||
|
"torrc, check whether it was specified on the command line "
|
||||||
|
"by a startup script.)", user);
|
||||||
|
} else {
|
||||||
|
log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
|
||||||
|
" as root.");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setegid(pw->pw_gid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting egid to %d: %s",
|
||||||
|
(int)pw->pw_gid, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setgid(pw->pw_gid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting gid to %d: %s",
|
||||||
|
(int)pw->pw_gid, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setuid(pw->pw_uid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
|
||||||
|
user, (int)pw->pw_uid, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seteuid(pw->pw_uid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
|
||||||
|
user, (int)pw->pw_uid, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is how OpenBSD rolls:
|
||||||
|
if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
|
||||||
|
setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
|
||||||
|
setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
|
||||||
|
log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
|
||||||
|
strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* We've properly switched egid, gid, euid, uid, and supplementary groups if
|
||||||
|
* we're here. */
|
||||||
|
#ifdef HAVE_LINUX_CAPABILITIES
|
||||||
|
if (keep_bindlow) {
|
||||||
|
if (drop_capabilities(0))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif /* defined(HAVE_LINUX_CAPABILITIES) */
|
||||||
|
|
||||||
|
#if !defined(CYGWIN) && !defined(__CYGWIN__)
|
||||||
|
/* If we tried to drop privilege to a group/user other than root, attempt to
|
||||||
|
* restore root (E)(U|G)ID, and abort if the operation succeeds */
|
||||||
|
|
||||||
|
/* Only check for privilege dropping if we were asked to be non-root */
|
||||||
|
if (pw->pw_uid) {
|
||||||
|
/* Try changing GID/EGID */
|
||||||
|
if (pw->pw_gid != old_gid &&
|
||||||
|
(setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
|
||||||
|
log_warn(LD_GENERAL, "Was able to restore group credentials even after "
|
||||||
|
"switching GID: this means that the setgid code didn't work.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try changing UID/EUID */
|
||||||
|
if (pw->pw_uid != old_uid &&
|
||||||
|
(setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
|
||||||
|
log_warn(LD_GENERAL, "Was able to restore user credentials even after "
|
||||||
|
"switching UID: this means that the setuid code didn't work.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
|
||||||
|
|
||||||
|
/* Check what really happened */
|
||||||
|
if (log_credential_status()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
have_already_switched_id = 1; /* mark success so we never try again */
|
||||||
|
|
||||||
|
#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
|
||||||
|
defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
|
||||||
|
if (pw->pw_uid) {
|
||||||
|
/* Re-enable core dumps if we're not running as root. */
|
||||||
|
log_info(LD_CONFIG, "Re-enabling coredumps");
|
||||||
|
if (prctl(PR_SET_DUMPABLE, 1)) {
|
||||||
|
log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#else /* !(!defined(_WIN32)) */
|
||||||
|
(void)user;
|
||||||
|
(void)flags;
|
||||||
|
|
||||||
|
log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
|
||||||
|
return -1;
|
||||||
|
#endif /* !defined(_WIN32) */
|
||||||
|
}
|
17
src/lib/process/setuid.h
Normal file
17
src/lib/process/setuid.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#ifndef TOR_SETUID_H
|
||||||
|
#define TOR_SETUID_H
|
||||||
|
|
||||||
|
int have_capability_support(void);
|
||||||
|
|
||||||
|
/** Flag for switch_id; see switch_id() for documentation */
|
||||||
|
#define SWITCH_ID_KEEP_BINDLOW (1<<0)
|
||||||
|
/** Flag for switch_id; see switch_id() for documentation */
|
||||||
|
#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
|
||||||
|
int switch_id(const char *user, unsigned flags);
|
||||||
|
|
||||||
|
#endif
|
1231
src/lib/process/subprocess.c
Normal file
1231
src/lib/process/subprocess.c
Normal file
File diff suppressed because it is too large
Load Diff
129
src/lib/process/subprocess.h
Normal file
129
src/lib/process/subprocess.h
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/* Copyright (c) 2003-2004, Roger Dingledine
|
||||||
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
||||||
|
* Copyright (c) 2007-2018, The Tor Project, Inc. */
|
||||||
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
|
#ifndef TOR_SUBPROCESS_H
|
||||||
|
#define TOR_SUBPROCESS_H
|
||||||
|
|
||||||
|
#include "lib/cc/torint.h"
|
||||||
|
#include "lib/testsupport/testsupport.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct smartlist_t;
|
||||||
|
|
||||||
|
void tor_disable_spawning_background_processes(void);
|
||||||
|
|
||||||
|
typedef struct process_handle_t process_handle_t;
|
||||||
|
struct process_environment_t;
|
||||||
|
int tor_spawn_background(const char *const filename, const char **argv,
|
||||||
|
struct process_environment_t *env,
|
||||||
|
process_handle_t **process_handle_out);
|
||||||
|
|
||||||
|
#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
|
||||||
|
|
||||||
|
/** Status of an I/O stream. */
|
||||||
|
enum stream_status {
|
||||||
|
IO_STREAM_OKAY,
|
||||||
|
IO_STREAM_EAGAIN,
|
||||||
|
IO_STREAM_TERM,
|
||||||
|
IO_STREAM_CLOSED
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *stream_status_to_string(enum stream_status stream_status);
|
||||||
|
|
||||||
|
enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
|
||||||
|
|
||||||
|
/* Values of process_handle_t.status. */
|
||||||
|
#define PROCESS_STATUS_NOTRUNNING 0
|
||||||
|
#define PROCESS_STATUS_RUNNING 1
|
||||||
|
#define PROCESS_STATUS_ERROR -1
|
||||||
|
|
||||||
|
#ifdef SUBPROCESS_PRIVATE
|
||||||
|
struct waitpid_callback_t;
|
||||||
|
|
||||||
|
/** Structure to represent the state of a process with which Tor is
|
||||||
|
* communicating. The contents of this structure are private to util.c */
|
||||||
|
struct process_handle_t {
|
||||||
|
/** One of the PROCESS_STATUS_* values */
|
||||||
|
int status;
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE stdin_pipe;
|
||||||
|
HANDLE stdout_pipe;
|
||||||
|
HANDLE stderr_pipe;
|
||||||
|
PROCESS_INFORMATION pid;
|
||||||
|
#else /* !(defined(_WIN32)) */
|
||||||
|
int stdin_pipe;
|
||||||
|
int stdout_pipe;
|
||||||
|
int stderr_pipe;
|
||||||
|
pid_t pid;
|
||||||
|
/** If the process has not given us a SIGCHLD yet, this has the
|
||||||
|
* waitpid_callback_t that gets invoked once it has. Otherwise this
|
||||||
|
* contains NULL. */
|
||||||
|
struct waitpid_callback_t *waitpid_cb;
|
||||||
|
/** The exit status reported by waitpid. */
|
||||||
|
int waitpid_exit_status;
|
||||||
|
#endif /* defined(_WIN32) */
|
||||||
|
};
|
||||||
|
#endif /* defined(SUBPROCESS_PRIVATE) */
|
||||||
|
|
||||||
|
/* Return values of tor_get_exit_code() */
|
||||||
|
#define PROCESS_EXIT_RUNNING 1
|
||||||
|
#define PROCESS_EXIT_EXITED 0
|
||||||
|
#define PROCESS_EXIT_ERROR -1
|
||||||
|
int tor_get_exit_code(process_handle_t *process_handle,
|
||||||
|
int block, int *exit_code);
|
||||||
|
int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
|
||||||
|
#ifdef _WIN32
|
||||||
|
ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
|
||||||
|
const process_handle_t *process);
|
||||||
|
#else
|
||||||
|
ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
|
||||||
|
const process_handle_t *process,
|
||||||
|
int *eof);
|
||||||
|
#endif /* defined(_WIN32) */
|
||||||
|
ssize_t tor_read_all_from_process_stdout(
|
||||||
|
const process_handle_t *process_handle, char *buf, size_t count);
|
||||||
|
ssize_t tor_read_all_from_process_stderr(
|
||||||
|
const process_handle_t *process_handle, char *buf, size_t count);
|
||||||
|
char *tor_join_win_cmdline(const char *argv[]);
|
||||||
|
|
||||||
|
int tor_process_get_pid(process_handle_t *process_handle);
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
|
||||||
|
#else
|
||||||
|
int tor_process_get_stdout_pipe(process_handle_t *process_handle);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(HANDLE *handle,
|
||||||
|
enum stream_status *stream_status));
|
||||||
|
#else
|
||||||
|
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd,
|
||||||
|
enum stream_status *stream_status));
|
||||||
|
#endif /* defined(_WIN32) */
|
||||||
|
|
||||||
|
int tor_terminate_process(process_handle_t *process_handle);
|
||||||
|
|
||||||
|
MOCK_DECL(void, tor_process_handle_destroy,(process_handle_t *process_handle,
|
||||||
|
int also_terminate_process));
|
||||||
|
|
||||||
|
#ifdef SUBPROCESS_PRIVATE
|
||||||
|
/* Prototypes for private functions only used by util.c (and unit tests) */
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
STATIC int format_helper_exit_status(unsigned char child_state,
|
||||||
|
int saved_errno, char *hex_errno);
|
||||||
|
|
||||||
|
/* Space for hex values of child state, a slash, saved_errno (with
|
||||||
|
leading minus) and newline (no null) */
|
||||||
|
#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
|
||||||
|
1 + sizeof(int) * 2 + 1)
|
||||||
|
#endif /* !defined(_WIN32) */
|
||||||
|
|
||||||
|
#endif /* defined(SUBPROCESS_PRIVATE) */
|
||||||
|
|
||||||
|
#endif
|
@ -12,18 +12,19 @@
|
|||||||
|
|
||||||
#include "orconfig.h"
|
#include "orconfig.h"
|
||||||
|
|
||||||
#ifdef HAVE_SYS_TYPES_H
|
#ifndef _WIN32
|
||||||
#include <sys/types.h>
|
|
||||||
#endif
|
#include "lib/process/waitpid.h"
|
||||||
|
#include "lib/log/torlog.h"
|
||||||
|
#include "lib/log/util_bug.h"
|
||||||
|
#include "lib/malloc/util_malloc.h"
|
||||||
|
#include "ht.h"
|
||||||
|
|
||||||
#ifdef HAVE_SYS_WAIT_H
|
#ifdef HAVE_SYS_WAIT_H
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "common/compat.h"
|
#include <string.h>
|
||||||
#include "common/util.h"
|
|
||||||
#include "lib/log/torlog.h"
|
|
||||||
#include "common/util_process.h"
|
|
||||||
#include "ht.h"
|
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
/* Convenience structures for handlers for waitpid().
|
/* Convenience structures for handlers for waitpid().
|
||||||
@ -32,8 +33,6 @@
|
|||||||
* monitoring a non-child process.
|
* monitoring a non-child process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
|
|
||||||
/** Mapping from a PID to a userfn/userdata pair. */
|
/** Mapping from a PID to a userfn/userdata pair. */
|
||||||
struct waitpid_callback_t {
|
struct waitpid_callback_t {
|
||||||
HT_ENTRY(waitpid_callback_t) node;
|
HT_ENTRY(waitpid_callback_t) node;
|
||||||
@ -155,4 +154,3 @@ notify_pending_waitpid_callbacks(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif /* !defined(_WIN32) */
|
#endif /* !defined(_WIN32) */
|
||||||
|
|
@ -2,14 +2,18 @@
|
|||||||
/* See LICENSE for licensing information */
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \file util_process.h
|
* \file waitpid.h
|
||||||
* \brief Headers for util_process.c
|
* \brief Headers for waitpid.c
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#ifndef TOR_UTIL_PROCESS_H
|
#ifndef TOR_WAITPID_H
|
||||||
#define TOR_UTIL_PROCESS_H
|
#define TOR_WAITPID_H
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/** A callback structure waiting for us to get a SIGCHLD informing us that a
|
/** A callback structure waiting for us to get a SIGCHLD informing us that a
|
||||||
* PID has been closed. Created by set_waitpid_callback. Cancelled or cleaned-
|
* PID has been closed. Created by set_waitpid_callback. Cancelled or cleaned-
|
||||||
* up from clear_waitpid_callback(). Do not access outside of the main thread;
|
* up from clear_waitpid_callback(). Do not access outside of the main thread;
|
||||||
@ -22,5 +26,4 @@ void clear_waitpid_callback(waitpid_callback_t *ent);
|
|||||||
void notify_pending_waitpid_callbacks(void);
|
void notify_pending_waitpid_callbacks(void);
|
||||||
#endif /* !defined(_WIN32) */
|
#endif /* !defined(_WIN32) */
|
||||||
|
|
||||||
#endif /* !defined(TOR_UTIL_PROCESS_H) */
|
#endif /* !defined(TOR_WAITPID_H) */
|
||||||
|
|
@ -111,6 +111,12 @@
|
|||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "lib/process/daemon.h"
|
||||||
|
#include "lib/process/pidfile.h"
|
||||||
|
#include "lib/process/restrict.h"
|
||||||
|
#include "lib/process/setuid.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
|
|
||||||
#include "lib/fs/conffile.h"
|
#include "lib/fs/conffile.h"
|
||||||
#include "common/procmon.h"
|
#include "common/procmon.h"
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
#include "or/status.h"
|
#include "or/status.h"
|
||||||
#include "or/tor_api.h"
|
#include "or/tor_api.h"
|
||||||
#include "or/tor_api_internal.h"
|
#include "or/tor_api_internal.h"
|
||||||
#include "common/util_process.h"
|
#include "lib/process/waitpid.h"
|
||||||
#include "or/ext_orport.h"
|
#include "or/ext_orport.h"
|
||||||
#include "lib/memarea/memarea.h"
|
#include "lib/memarea/memarea.h"
|
||||||
#include "lib/sandbox/sandbox.h"
|
#include "lib/sandbox/sandbox.h"
|
||||||
|
@ -102,6 +102,9 @@
|
|||||||
#include "or/ext_orport.h"
|
#include "or/ext_orport.h"
|
||||||
#include "or/control.h"
|
#include "or/control.h"
|
||||||
|
|
||||||
|
#include "lib/process/env.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
|
|
||||||
static process_environment_t *
|
static process_environment_t *
|
||||||
create_managed_proxy_environment(const managed_proxy_t *mp);
|
create_managed_proxy_environment(const managed_proxy_t *mp);
|
||||||
|
|
||||||
@ -1696,4 +1699,3 @@ pt_free_all(void)
|
|||||||
managed_proxy_list=NULL;
|
managed_proxy_list=NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,8 @@ enum pt_proto_state {
|
|||||||
PT_PROTO_FAILED_LAUNCH /* failed while launching */
|
PT_PROTO_FAILED_LAUNCH /* failed while launching */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct process_handle_t;
|
||||||
|
|
||||||
/** Structure containing information of a managed proxy. */
|
/** Structure containing information of a managed proxy. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
enum pt_proto_state conf_state; /* the current configuration state */
|
enum pt_proto_state conf_state; /* the current configuration state */
|
||||||
@ -90,7 +92,7 @@ typedef struct {
|
|||||||
int is_server; /* is it a server proxy? */
|
int is_server; /* is it a server proxy? */
|
||||||
|
|
||||||
/* A pointer to the process handle of this managed proxy. */
|
/* A pointer to the process handle of this managed proxy. */
|
||||||
process_handle_t *process_handle;
|
struct process_handle_t *process_handle;
|
||||||
|
|
||||||
int pid; /* The Process ID this managed proxy is using. */
|
int pid; /* The Process ID this managed proxy is using. */
|
||||||
|
|
||||||
@ -140,4 +142,3 @@ STATIC void free_execve_args(char **arg);
|
|||||||
#endif /* defined(PT_PRIVATE) */
|
#endif /* defined(PT_PRIVATE) */
|
||||||
|
|
||||||
#endif /* !defined(TOR_TRANSPORTS_H) */
|
#endif /* !defined(TOR_TRANSPORTS_H) */
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "lib/err/torerr.h"
|
#include "lib/err/torerr.h"
|
||||||
#include "lib/log/torlog.h"
|
#include "lib/log/torlog.h"
|
||||||
#include "test/test.h"
|
#include "test/test.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dummy_cb_fn(int severity, uint32_t domain, const char *msg)
|
dummy_cb_fn(int severity, uint32_t domain, const char *msg)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#define UTIL_PRIVATE
|
#define UTIL_PRIVATE
|
||||||
#define STATEFILE_PRIVATE
|
#define STATEFILE_PRIVATE
|
||||||
#define CONTROL_PRIVATE
|
#define CONTROL_PRIVATE
|
||||||
|
#define SUBPROCESS_PRIVATE
|
||||||
#include "or/or.h"
|
#include "or/or.h"
|
||||||
#include "or/config.h"
|
#include "or/config.h"
|
||||||
#include "or/confparse.h"
|
#include "or/confparse.h"
|
||||||
@ -17,6 +18,7 @@
|
|||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "or/statefile.h"
|
#include "or/statefile.h"
|
||||||
#include "test/test.h"
|
#include "test/test.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
reset_mp(managed_proxy_t *mp)
|
reset_mp(managed_proxy_t *mp)
|
||||||
@ -544,4 +546,3 @@ struct testcase_t pt_tests[] = {
|
|||||||
NULL, NULL },
|
NULL, NULL },
|
||||||
END_OF_TESTCASES
|
END_OF_TESTCASES
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/* See LICENSE for licensing information */
|
/* See LICENSE for licensing information */
|
||||||
|
|
||||||
#include "or/or.h"
|
#include "or/or.h"
|
||||||
|
#include "lib/process/setuid.h"
|
||||||
|
|
||||||
#ifdef HAVE_SYS_CAPABILITY_H
|
#ifdef HAVE_SYS_CAPABILITY_H
|
||||||
#include <sys/capability.h>
|
#include <sys/capability.h>
|
||||||
@ -189,4 +190,3 @@ main(int argc, char **argv)
|
|||||||
return (okay ? 0 : 1);
|
return (okay ? 0 : 1);
|
||||||
#endif /* defined(_WIN32) */
|
#endif /* defined(_WIN32) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define UTIL_PRIVATE
|
#define UTIL_PRIVATE
|
||||||
#define UTIL_MALLOC_PRIVATE
|
#define UTIL_MALLOC_PRIVATE
|
||||||
#define SOCKET_PRIVATE
|
#define SOCKET_PRIVATE
|
||||||
|
#define SUBPROCESS_PRIVATE
|
||||||
#include "or/or.h"
|
#include "or/or.h"
|
||||||
#include "common/buffers.h"
|
#include "common/buffers.h"
|
||||||
#include "or/config.h"
|
#include "or/config.h"
|
||||||
@ -17,10 +18,13 @@
|
|||||||
#include "lib/crypt_ops/crypto_rand.h"
|
#include "lib/crypt_ops/crypto_rand.h"
|
||||||
#include "test/test.h"
|
#include "test/test.h"
|
||||||
#include "lib/memarea/memarea.h"
|
#include "lib/memarea/memarea.h"
|
||||||
#include "common/util_process.h"
|
#include "lib/process/waitpid.h"
|
||||||
#include "test/log_test_helpers.h"
|
#include "test/log_test_helpers.h"
|
||||||
#include "lib/compress/compress_zstd.h"
|
#include "lib/compress/compress_zstd.h"
|
||||||
#include "lib/fdio/fdio.h"
|
#include "lib/fdio/fdio.h"
|
||||||
|
#include "lib/process/env.h"
|
||||||
|
#include "lib/process/pidfile.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
|
|
||||||
#ifdef HAVE_PWD_H
|
#ifdef HAVE_PWD_H
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "test/test.h"
|
#include "test/test.h"
|
||||||
|
|
||||||
#include "common/util_process.h"
|
#include "lib/process/waitpid.h"
|
||||||
|
|
||||||
#include "test/log_test_helpers.h"
|
#include "test/log_test_helpers.h"
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
#include "orconfig.h"
|
#include "orconfig.h"
|
||||||
#define UTIL_PRIVATE
|
#define UTIL_PRIVATE
|
||||||
|
#define SUBPROCESS_PRIVATE
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/util_process.h"
|
#include "lib/process/waitpid.h"
|
||||||
#include "lib/crypt_ops/crypto.h"
|
#include "lib/crypt_ops/crypto.h"
|
||||||
#include "lib/log/torlog.h"
|
#include "lib/log/torlog.h"
|
||||||
|
#include "lib/process/subprocess.h"
|
||||||
#include "test/test.h"
|
#include "test/test.h"
|
||||||
|
|
||||||
#ifndef BUILDDIR
|
#ifndef BUILDDIR
|
||||||
@ -388,4 +390,3 @@ struct testcase_t slow_util_tests[] = {
|
|||||||
UTIL_TEST(spawn_background_waitpid_notify, 0),
|
UTIL_TEST(spawn_background_waitpid_notify, 0),
|
||||||
END_OF_TESTCASES
|
END_OF_TESTCASES
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user