summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShawn Landden <slandden@gmail.com>2018-02-26 15:24:04 -0800
committerGitHub <noreply@github.com>2018-02-26 15:24:04 -0800
commitd849ca2bcf67126aedd09a649f1a402cd29ac46a (patch)
tree8b134101a4b243f7e7ca6402ed4580bf59ccb388
parent23d362910b1a6b2dca8a8d49e84620dce7a1345a (diff)
parent920e8b922addea8c54e68cc29c1416753f532f78 (diff)
downloaddistcc-git-d849ca2bcf67126aedd09a649f1a402cd29ac46a.tar.gz
Merge pull request #243 from shawnl/access
access control on having masquerade set up for binary
-rw-r--r--INSTALL11
-rw-r--r--Makefile.in13
-rw-r--r--man/distcc.15
-rw-r--r--man/distccd.19
-rw-r--r--src/daemon.c21
-rw-r--r--src/dopt.c7
-rw-r--r--src/dopt.h1
-rw-r--r--src/serve.c39
-rwxr-xr-xtest/testdistcc.py8
-rwxr-xr-xupdate-distcc-symlinks.py62
10 files changed, 167 insertions, 9 deletions
diff --git a/INSTALL b/INSTALL
index 8a8e04f..0f9577c 100644
--- a/INSTALL
+++ b/INSTALL
@@ -18,12 +18,13 @@ QUICK SUMMARY
1. Build and install
- ./autogen.sh # If "configure" does not already exist.
+ ./autogen.sh # If "configure" does not already exist.
./configure
make
- make check # Optional! Should have python >= 3.1 installed.
- make install # You may need to use "sudo" for this command.
- make installcheck # Optional! Should have python >= 3.1 installed.
+ make check # Optional! Should have python >= 3.1 installed.
+ make install # You may need to use "sudo" for this command.
+ make installcheck # Optional! Should have python >= 3.1 installed.
+ update-distcc-symlinks.py # Needs "sudo". Run this again if you install/remove compilers
Repeat installation for each server machine.
@@ -40,6 +41,8 @@ QUICK SUMMARY
cd ~/my_sources/my_project
pump make -j40 CC="distcc gcc" <your target>
+5. If you run into problems it is highly recommended to use DISTCC_VERBOSE=1
+ on the client and "--log-level debug" on the server.
DETAILED INSTRUCTIONS
=====================
diff --git a/Makefile.in b/Makefile.in
index 3ea595c..a1aca1c 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -417,6 +417,9 @@ bin_PROGRAMS = \
lsdistcc@EXEEXT@ \
@GNOME_BIN@
+sbin_PROGRAMS = \
+ update-distcc-symlinks.py
+
check_PROGRAMS = \
h_argvtostr@EXEEXT@ \
h_exten@EXEEXT@ \
@@ -1022,6 +1025,7 @@ showpaths:
@echo " man pages $(DESTDIR)$(man1dir)"
@echo " documents $(DESTDIR)$(docdir)"
@echo " programs $(DESTDIR)$(bindir)"
+ @echo " sbin programs $(DESTDIR)$(sbindir)"
@echo " system configuration $(DESTDIR)$(sysconfdir)"
@echo " shared data files $(DESTDIR)$(pkgdatadir)"
@@ -1034,9 +1038,13 @@ install: showpaths install-doc install-man install-programs \
install-programs: $(bin_PROGRAMS)
$(mkinstalldirs) "$(DESTDIR)$(bindir)"
+ $(mkinstalldirs) "$(DESTDIR)$(sbindir)"
for p in $(bin_PROGRAMS); do \
$(INSTALL_PROGRAM) "$$p" "$(DESTDIR)$(bindir)" || exit 1; \
done
+ for p in $(sbin_PROGRAMS); do \
+ $(INSTALL_PROGRAM) "$$p" "$(DESTDIR)$(sbindir)" || exit 1; \
+ done
# See comments for the include-server target. Also, we work around an issue in
# the change_root function of distutils/utils.py that turns the absolute prefix
@@ -1161,7 +1169,12 @@ uninstall-programs:
file="$(DESTDIR)$(bindir)/`basename $$p`"; \
if [ -e "$$file" ]; then rm -fv "$$file"; fi \
done
+ for p in $(sbin_PROGRAMS); do \
+ file="$(DESTDIR)$(sbindir)/`basename $$p`"; \
+ if [ -e "$$file" ]; then rm -fv "$$file"; fi \
+ done
-[ "`basename $(bindir)`" = "$(PACKAGE)" ] && rmdir "$(DESTDIR)$(bindir)"
+ -[ "`basename $(sbindir)`" = "$(PACKAGE)" ] && rmdir "$(DESTDIR)$(sbindir)"
# There's no setup.py --uninstall. :-( So I depend on
# PYTHON_INSTALL_RECORD being set. If it was used at --install time,
diff --git a/man/distcc.1 b/man/distcc.1
index 9ab9afb..18e5737 100644
--- a/man/distcc.1
+++ b/man/distcc.1
@@ -371,6 +371,11 @@ Then, to use distcc, a user just needs to put the directory
/usr/lib/distcc/bin early in the PATH, and have set a host list in
DISTCC_HOSTS or a file. distcc will handle the rest.
.PP
+To automatically discover compilers and create masquerade links run
+the provided
+.BR update-distcc-symlinks.py
+script.
+.PP
Note that this masquerade directory must occur on the PATH earlier
than the directory that contains the actual compilers of the same
names, and that any auxiliary programs that these compilers call (such
diff --git a/man/distccd.1 b/man/distccd.1
index 41596d8..75d0339 100644
--- a/man/distccd.1
+++ b/man/distccd.1
@@ -209,6 +209,15 @@ assumes daemon mode at startup if stdin is a tty, so
starting distccd from a script or in a non-interactive
ssh connection.
.TP
+.B --make-me-a-botnet
+By default (since Distcc 3.2) distcc will only execute binaries
+that are masqueraded to distcc in /usr/lib/distcc. This turns
+that off, and opens distcc up to executing arbitrary code. This
+feature is mainly for distcc's test suite and is called
+.B --make-me-a-botnet
+for a reason. See MASQUERADING of
+.BR distcc (1).
+.TP
.B --zeroconf
Register the availability of this distccd server using Avahi Zeroconf
DNS Service Discovery (DNS-SD). This allows distcc clients on the local
diff --git a/src/daemon.c b/src/daemon.c
index a1056f8..2127695 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -44,6 +44,7 @@
#include <config.h>
+#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -151,6 +152,23 @@ static int dcc_setup_daemon_path(void)
}
}
+static void dcc_warn_masquerade_whitelist(void) {
+ DIR *d;
+ const char *warn = "You must see up masquerade" \
+ " (see distcc(1)) to list whitelisted compilers or pass" \
+ " --make-me-a-botnet. To set up masquerade automatically" \
+ " run update-distcc-symlinks.py.";
+
+ d = opendir("/usr/lib/distcc");
+ if (!d) {
+ rs_log_crit("/usr/lib/distcc not found. %s", warn);
+ dcc_exit(EXIT_COMPILER_MISSING);
+ }
+ if (!readdir(d)) {
+ rs_log_crit("/usr/lib/distcc empty. %s", warn);
+ dcc_exit(EXIT_COMPILER_MISSING);
+ }
+}
/**
* distcc daemon. May run from inetd, or standalone. Accepts
@@ -227,6 +245,9 @@ int main(int argc, char *argv[])
/* Initialize the distcc io timeout value */
dcc_get_io_timeout();
+ if (!opt_make_me_a_botnet)
+ dcc_warn_masquerade_whitelist();
+
if (dcc_should_be_inetd())
ret = dcc_inetd_server();
else
diff --git a/src/dopt.c b/src/dopt.c
index 9c36daa..a3eb240 100644
--- a/src/dopt.c
+++ b/src/dopt.c
@@ -93,6 +93,12 @@ int opt_log_stderr = 0;
int opt_log_level_num = RS_LOG_NOTICE;
/**
+ * If true, do not check if a link to distcc exists in /usr/lib/distcc
+ * for every program executed remotely.
+ **/
+int opt_make_me_a_botnet = 0;
+
+/**
* Daemon exits after this many seconds. Intended mainly for testing, to make
* sure daemons don't persist for too long.
*/
@@ -152,6 +158,7 @@ const struct poptOption options[] = {
#ifdef HAVE_AVAHI
{ "zeroconf", 0, POPT_ARG_NONE, &opt_zeroconf, 0, 0, 0 },
#endif
+ { "make-me-a-botnet", 0, POPT_ARG_NONE, &opt_make_me_a_botnet, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 }
};
diff --git a/src/dopt.h b/src/dopt.h
index 695bc1e..8cb92e0 100644
--- a/src/dopt.h
+++ b/src/dopt.h
@@ -36,6 +36,7 @@ extern int opt_no_fork;
extern int opt_no_prefork;
extern int opt_no_detach;
extern int opt_daemon_mode, opt_inetd_mode;
+extern int opt_make_me_a_botnet;
extern int opt_job_lifetime;
extern const char *arg_log_file;
extern int opt_no_fifo;
diff --git a/src/serve.c b/src/serve.c
index 336a491..d998003 100644
--- a/src/serve.c
+++ b/src/serve.c
@@ -357,6 +357,40 @@ static int dcc_check_compiler_masq(char *compiler_name)
return 0;
}
+/**
+ * Make sure there is a masquerade to distcc in /usr/lib/distcc in order to
+ * execute a binary of the same name.
+ *
+ * Before this it was possible to execute arbitrary command after connecting
+ * to distcc, which is quite a security risk when combined with any local root
+ * privledge escalation exploit. See CVE 2004-2687
+ *
+ * https://nvd.nist.gov/vuln/detail/CVE-2004-2687
+ * https://github.com/distcc/distcc/issues/155
+ **/
+static int dcc_check_compiler_whitelist(char *compiler_name)
+{
+ int dirfd = -1;
+
+ if (strchr(compiler_name, '/'))
+ return EXIT_BAD_ARGUMENTS;
+
+ dirfd = open("/usr/lib/distcc", O_RDONLY);
+ if (dirfd < 0) {
+ if (errno == ENOENT)
+ rs_log_crit("no %s", "/usr/lib/distcc");
+ return EXIT_DISTCC_FAILED;
+ }
+
+ if (faccessat(dirfd, compiler_name, X_OK, 0) < 0) {
+ rs_log_crit("%s not in %s whitelist.", compiler_name, "/usr/lib/distcc");
+ return EXIT_BAD_ARGUMENTS; /* ENOENT, EACCESS, etc */
+ }
+
+ rs_trace("%s in /usr/lib/distcc whitelist", compiler_name);
+ return 0;
+}
+
static const char *include_options[] = {
"-I",
"-include",
@@ -669,11 +703,14 @@ static int dcc_run_job(int in_fd,
}
if (!dcc_remap_compiler(&argv[0]))
- goto out_cleanup;
+ goto out_cleanup;
if ((ret = dcc_check_compiler_masq(argv[0])))
goto out_cleanup;
+ if (!opt_make_me_a_botnet && dcc_check_compiler_whitelist(argv[0]))
+ goto out_cleanup;
+
if ((compile_ret = dcc_spawn_child(argv, &cc_pid,
"/dev/null", out_fname, err_fname))
|| (compile_ret = dcc_collect_child("cc", cc_pid, &status, in_fd))) {
diff --git a/test/testdistcc.py b/test/testdistcc.py
index ef0e351..b1d2fda 100755
--- a/test/testdistcc.py
+++ b/test/testdistcc.py
@@ -345,7 +345,7 @@ as soon as that happens we can go ahead and start the client."""
"""Return command to start the daemon"""
return (self.distccd() +
"--verbose --lifetime=%d --daemon --log-file %s "
- "--pid-file %s --port %d --allow 127.0.0.1"
+ "--pid-file %s --port %d --allow 127.0.0.1 --make-me-a-botnet"
% (self.daemon_lifetime(),
_ShellSafe(self.daemon_logfile),
_ShellSafe(self.daemon_pidfile),
@@ -815,7 +815,7 @@ class DaemonBadPort_Case(SimpleDistCC_Case):
"""Test daemon invoked with invalid port number"""
self.runcmd(self.distccd() +
"--log-file=distccd.log --lifetime=10 --port 80000 "
- "--allow 127.0.0.1",
+ "--allow 127.0.0.1 --make-me-a-botnet",
EXIT_BAD_ARGUMENTS)
self.assert_no_file("daemonpid.tmp")
@@ -1546,7 +1546,7 @@ class NoDetachDaemon_Case(CompileHello_Case):
# port as an existing server, because we can't catch the error.
cmd = (self.distccd() +
"--no-detach --daemon --verbose --log-file %s --pid-file %s "
- "--port %d --allow 127.0.0.1" %
+ "--port %d --allow 127.0.0.1 --make-me-a-botnet" %
(_ShellSafe(self.daemon_logfile),
_ShellSafe(self.daemon_pidfile),
self.server_port))
@@ -2046,7 +2046,7 @@ class AccessDenied_Case(CompileHello_Case):
def daemon_command(self):
return (self.distccd()
+ "--verbose --lifetime=%d --daemon --log-file %s "
- "--pid-file %s --port %d --allow 127.0.0.2"
+ "--pid-file %s --port %d --allow 127.0.0.2 --make-me-a-botnet"
% (self.daemon_lifetime(),
_ShellSafe(self.daemon_logfile),
_ShellSafe(self.daemon_pidfile),
diff --git a/update-distcc-symlinks.py b/update-distcc-symlinks.py
new file mode 100755
index 0000000..f467ead
--- /dev/null
+++ b/update-distcc-symlinks.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+import subprocess, string, os, stat, re
+
+distcc_dir = "/usr/lib/distcc"
+gcc_dir = "/usr/lib/gcc"
+old_symlinks = set()
+new_symlinks = set()
+standard_names = ["cc", "c++", "c89", "c99"]
+
+if not os.access(distcc_dir, os.X_OK):
+ os.mkdir(distcc_dir)
+
+def consider(name):
+ if os.access("/usr/bin/%(name)s" % vars(), os.X_OK):
+ new_symlinks.add(name)
+ print(name)
+
+def consider_gcc(prefix, suffix):
+ consider("%(prefix)sgcc%(suffix)s" % vars())
+ consider("%(prefix)sg++%(suffix)s" % vars())
+
+def consider_clang(suffix):
+ consider("clang%(suffix)s" % vars())
+ consider("clang++%(suffix)s" % vars())
+
+for x in standard_names:
+ consider(x)
+
+consider_gcc("", "")
+consider_gcc("c89-", "")
+consider_gcc("c99-", "")
+for gnu_host in os.listdir(gcc_dir):
+ consider_gcc("%(gnu_host)s-" % vars(), "")
+ for version in os.listdir(gcc_dir + "/" + gnu_host):
+ consider_gcc("", "-%(version)s" % vars())
+ consider_gcc("%(gnu_host)s-" % vars(), "-%(version)s" % vars())
+
+consider_clang("")
+for ent in os.listdir("/usr/lib"):
+ if ent.startswith("llvm-"):
+ version = ent.split("-")[1]
+ consider_clang("-%(version)s" % vars())
+
+for name in os.listdir(distcc_dir):
+ mode = os.lstat(distcc_dir + "/" + name).st_mode
+ if stat.S_ISLNK(mode):
+ if os.access(distcc_dir + "/" + name, os.X_OK):
+ old_symlinks.add(name)
+ else:
+ os.unlink(distcc_dir + "/" + name)
+
+for link in old_symlinks:
+ if link not in new_symlinks:
+ os.unlink(distcc_dir + "/" + link)
+
+for link in new_symlinks:
+ if link not in old_symlinks:
+ if os.access("/usr/bin/distcc", os.X_OK):
+ os.symlink("../../bin/distcc", distcc_dir + "/" + link)
+ else:
+ os.symlink("../../local/bin/distcc", distcc_dir + "/" + link)