forked from lix-project/lix
Remove nix-setuid-helper
AFAIK, nobody uses it, it's not maintained, and it has no tests.
This commit is contained in:
parent
89e6781cc5
commit
a478e8a7bb
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -100,9 +100,6 @@ Makefile.in
|
||||||
/src/nix-log2xml/*.xml
|
/src/nix-log2xml/*.xml
|
||||||
/src/nix-log2xml/*.html
|
/src/nix-log2xml/*.html
|
||||||
|
|
||||||
# /src/nix-setuid-helper/
|
|
||||||
/src/nix-setuid-helper/nix-setuid-helper
|
|
||||||
|
|
||||||
# /src/nix-store/
|
# /src/nix-store/
|
||||||
/src/nix-store/nix-store
|
/src/nix-store/nix-store
|
||||||
|
|
||||||
|
|
|
@ -375,7 +375,6 @@ AC_CONFIG_FILES([Makefile
|
||||||
src/nix-instantiate/Makefile
|
src/nix-instantiate/Makefile
|
||||||
src/nix-env/Makefile
|
src/nix-env/Makefile
|
||||||
src/nix-daemon/Makefile
|
src/nix-daemon/Makefile
|
||||||
src/nix-setuid-helper/Makefile
|
|
||||||
src/nix-log2xml/Makefile
|
src/nix-log2xml/Makefile
|
||||||
src/bsdiff-4.3/Makefile
|
src/bsdiff-4.3/Makefile
|
||||||
perl/Makefile
|
perl/Makefile
|
||||||
|
|
|
@ -380,7 +380,7 @@ group should be the build users group, and it should have the sticky
|
||||||
bit turned on (like <filename>/tmp</filename>):
|
bit turned on (like <filename>/tmp</filename>):
|
||||||
|
|
||||||
<screen>
|
<screen>
|
||||||
$ chgrp nixbld /nix/store
|
$ chown root.nixbld /nix/store
|
||||||
$ chmod 1775 /nix/store
|
$ chmod 1775 /nix/store
|
||||||
</screen>
|
</screen>
|
||||||
|
|
||||||
|
@ -401,15 +401,7 @@ build-users-group = nixbld
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<section><title>Nix store/database owned by root</title>
|
<section><title>Running the daemon</title>
|
||||||
|
|
||||||
<para>The simplest setup is to let <literal>root</literal> own the Nix
|
|
||||||
store and database. I.e.,
|
|
||||||
|
|
||||||
<screen>
|
|
||||||
$ chown -R root /nix/store /nix/var/nix</screen>
|
|
||||||
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>The <link linkend="sec-nix-daemon">Nix daemon</link> should be
|
<para>The <link linkend="sec-nix-daemon">Nix daemon</link> should be
|
||||||
started as follows (as <literal>root</literal>):
|
started as follows (as <literal>root</literal>):
|
||||||
|
@ -433,72 +425,6 @@ into the users’ login scripts.</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<section><title>Nix store/database not owned by root</title>
|
|
||||||
|
|
||||||
<para>It is also possible to let the Nix store and database be owned
|
|
||||||
by a non-root user, which should be more secure<footnote><para>Note
|
|
||||||
however that even when the Nix daemon runs as root, not
|
|
||||||
<emphasis>that</emphasis> much code is executed as root: Nix
|
|
||||||
expression evaluation is performed by the calling (unprivileged) user,
|
|
||||||
and builds are performed under the special build user accounts. So
|
|
||||||
only the code that accesses the database and starts builds is executed
|
|
||||||
as <literal>root</literal>.</para></footnote>. Typically, this user
|
|
||||||
is a special account called <literal>nix</literal>, but it can be
|
|
||||||
named anything. It should own the Nix store and database:
|
|
||||||
|
|
||||||
<screen>
|
|
||||||
$ chown -R nix /nix/store /nix/var/nix</screen>
|
|
||||||
|
|
||||||
and of course <command>nix-daemon</command> should be started under
|
|
||||||
that user, e.g.,
|
|
||||||
|
|
||||||
<screen>
|
|
||||||
$ su - nix -c "exec /nix/bin/nix-daemon"</screen>
|
|
||||||
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>There is a catch, though: non-<literal>root</literal> users
|
|
||||||
cannot start builds under the build user accounts, since the
|
|
||||||
<function>setuid</function> system call is obviously privileged. To
|
|
||||||
allow a non-<literal>root</literal> Nix daemon to use the build user
|
|
||||||
feature, it calls a setuid-root helper program,
|
|
||||||
<command>nix-setuid-helper</command>. This program is installed in
|
|
||||||
<filename><replaceable>prefix</replaceable>/libexec/nix-setuid-helper</filename>.
|
|
||||||
To set the permissions properly (Nix’s <command>make install</command>
|
|
||||||
doesn’t do this, since we don’t want to ship setuid-root programs
|
|
||||||
out-of-the-box):
|
|
||||||
|
|
||||||
<screen>
|
|
||||||
$ chown root.root /nix/libexec/nix-setuid-helper
|
|
||||||
$ chmod 4755 /nix/libexec/nix-setuid-helper
|
|
||||||
</screen>
|
|
||||||
|
|
||||||
(This example assumes that the Nix binaries are installed in
|
|
||||||
<filename>/nix</filename>.)</para>
|
|
||||||
|
|
||||||
<para>Of course, the <command>nix-setuid-helper</command> command
|
|
||||||
should not be usable by just anybody, since then anybody could run
|
|
||||||
commands under the Nix build user accounts. For that reason there is
|
|
||||||
a configuration file <filename>/etc/nix-setuid.conf</filename> that
|
|
||||||
restricts the use of the helper. This file should be a text file
|
|
||||||
containing precisely two lines, the first being the Nix daemon user
|
|
||||||
and the second being the build users group, e.g.,
|
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
nix
|
|
||||||
nixbld
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
The setuid-helper barfs if it is called by a user other than the one
|
|
||||||
specified on the first line, or if it is asked to execute a build
|
|
||||||
under a user who is not a member of the group specified on the second
|
|
||||||
line. The file <filename>/etc/nix-setuid.conf</filename> must be
|
|
||||||
owned by root, and must not be group- or world-writable. The
|
|
||||||
setuid-helper barfs if this is not the case.</para>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<section><title>Restricting access</title>
|
<section><title>Restricting access</title>
|
||||||
|
|
||||||
<para>To limit which users can perform Nix operations, you can use the
|
<para>To limit which users can perform Nix operations, you can use the
|
||||||
|
|
|
@ -5,6 +5,22 @@
|
||||||
<title>Nix Release Notes</title>
|
<title>Nix Release Notes</title>
|
||||||
|
|
||||||
|
|
||||||
|
<!--==================================================================-->
|
||||||
|
|
||||||
|
<section xml:id="ssec-relnotes-1.7"><title>Release 1.7 (TBA)</title>
|
||||||
|
|
||||||
|
<para>This release has the following changes:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
|
||||||
|
<listitem><para><command>nix-setuid-helper</command> is
|
||||||
|
gone.</para></listitem>
|
||||||
|
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<!--==================================================================-->
|
<!--==================================================================-->
|
||||||
|
|
||||||
<section xml:id="ssec-relnotes-1.6.1"><title>Release 1.6.1 (October 28, 2013)</title>
|
<section xml:id="ssec-relnotes-1.6.1"><title>Release 1.6.1 (October 28, 2013)</title>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
SUBDIRS = boost libutil libstore libmain nix-store nix-hash \
|
SUBDIRS = boost libutil libstore libmain nix-store nix-hash \
|
||||||
libexpr nix-instantiate nix-env nix-daemon nix-setuid-helper \
|
libexpr nix-instantiate nix-env nix-daemon \
|
||||||
nix-log2xml bsdiff-4.3
|
nix-log2xml bsdiff-4.3
|
||||||
|
|
|
@ -549,93 +549,10 @@ void UserLock::release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void runSetuidHelper(const string & command,
|
|
||||||
const string & arg)
|
|
||||||
{
|
|
||||||
Path program = getEnv("NIX_SETUID_HELPER",
|
|
||||||
settings.nixLibexecDir + "/nix-setuid-helper");
|
|
||||||
|
|
||||||
/* Fork. */
|
|
||||||
Pid pid;
|
|
||||||
pid = fork();
|
|
||||||
switch (pid) {
|
|
||||||
|
|
||||||
case -1:
|
|
||||||
throw SysError("unable to fork");
|
|
||||||
|
|
||||||
case 0: /* child */
|
|
||||||
try {
|
|
||||||
std::vector<const char *> args; /* careful with c_str()! */
|
|
||||||
args.push_back(program.c_str());
|
|
||||||
args.push_back(command.c_str());
|
|
||||||
args.push_back(arg.c_str());
|
|
||||||
args.push_back(0);
|
|
||||||
|
|
||||||
restoreSIGPIPE();
|
|
||||||
restoreAffinity();
|
|
||||||
|
|
||||||
execve(program.c_str(), (char * *) &args[0], 0);
|
|
||||||
throw SysError(format("executing `%1%'") % program);
|
|
||||||
}
|
|
||||||
catch (std::exception & e) {
|
|
||||||
writeToStderr("error: " + string(e.what()) + "\n");
|
|
||||||
}
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parent. */
|
|
||||||
|
|
||||||
/* Wait for the child to finish. */
|
|
||||||
int status = pid.wait(true);
|
|
||||||
if (!statusOk(status))
|
|
||||||
throw Error(format("program `%1%' %2%")
|
|
||||||
% program % statusToString(status));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void UserLock::kill()
|
void UserLock::kill()
|
||||||
{
|
{
|
||||||
assert(enabled());
|
assert(enabled());
|
||||||
if (amPrivileged())
|
killUser(uid);
|
||||||
killUser(uid);
|
|
||||||
else
|
|
||||||
runSetuidHelper("kill", user);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool amPrivileged()
|
|
||||||
{
|
|
||||||
return geteuid() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void getOwnership(const Path & path)
|
|
||||||
{
|
|
||||||
runSetuidHelper("get-ownership", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void deletePathWrapped(const Path & path, unsigned long long & bytesFreed)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
/* First try to delete it ourselves. */
|
|
||||||
deletePath(path, bytesFreed);
|
|
||||||
} catch (SysError & e) {
|
|
||||||
/* If this failed due to a permission error, then try it with
|
|
||||||
the setuid helper. */
|
|
||||||
if (settings.buildUsersGroup != "" && !amPrivileged()) {
|
|
||||||
getOwnership(path);
|
|
||||||
deletePath(path, bytesFreed);
|
|
||||||
} else
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void deletePathWrapped(const Path & path)
|
|
||||||
{
|
|
||||||
unsigned long long dummy1;
|
|
||||||
deletePathWrapped(path, dummy1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -971,15 +888,11 @@ void DerivationGoal::killChild()
|
||||||
worker.childTerminated(pid);
|
worker.childTerminated(pid);
|
||||||
|
|
||||||
if (buildUser.enabled()) {
|
if (buildUser.enabled()) {
|
||||||
/* We can't use pid.kill(), since we may not have the
|
/* If we're using a build user, then there is a tricky
|
||||||
appropriate privilege. I.e., if we're not root, then
|
race condition: if we kill the build user before the
|
||||||
setuid helper should do it).
|
child has done its setuid() to the build user uid, then
|
||||||
|
it won't be killed, and we'll potentially lock up in
|
||||||
Also, if we're using a build user, then there is a
|
pid.wait(). So also send a conventional kill to the
|
||||||
tricky race condition: if we kill the build user before
|
|
||||||
the child has done its setuid() to the build user uid,
|
|
||||||
then it won't be killed, and we'll potentially lock up
|
|
||||||
in pid.wait(). So also send a conventional kill to the
|
|
||||||
child. */
|
child. */
|
||||||
::kill(-pid, SIGKILL); /* ignore the result */
|
::kill(-pid, SIGKILL); /* ignore the result */
|
||||||
buildUser.kill();
|
buildUser.kill();
|
||||||
|
@ -1349,7 +1262,7 @@ void DerivationGoal::tryToBuild()
|
||||||
if (worker.store.isValidPath(path)) continue;
|
if (worker.store.isValidPath(path)) continue;
|
||||||
if (!pathExists(path)) continue;
|
if (!pathExists(path)) continue;
|
||||||
debug(format("removing unregistered path `%1%'") % path);
|
debug(format("removing unregistered path `%1%'") % path);
|
||||||
deletePathWrapped(path);
|
deletePath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check again whether any output previously failed to build,
|
/* Check again whether any output previously failed to build,
|
||||||
|
@ -1427,7 +1340,7 @@ void replaceValidPath(const Path & storePath, const Path tmpPath)
|
||||||
if (rename(tmpPath.c_str(), storePath.c_str()) == -1)
|
if (rename(tmpPath.c_str(), storePath.c_str()) == -1)
|
||||||
throw SysError(format("moving `%1%' to `%2%'") % tmpPath % storePath);
|
throw SysError(format("moving `%1%' to `%2%'") % tmpPath % storePath);
|
||||||
if (pathExists(oldPath))
|
if (pathExists(oldPath))
|
||||||
deletePathWrapped(oldPath);
|
deletePath(oldPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1532,13 +1445,6 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
rewrittenPaths.insert(path);
|
rewrittenPaths.insert(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gain ownership of the build result using the setuid
|
|
||||||
wrapper if we're not root. If we *are* root, then
|
|
||||||
canonicalisePathMetaData() will take care of this later
|
|
||||||
on. */
|
|
||||||
if (buildUser.enabled() && !amPrivileged())
|
|
||||||
getOwnership(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check the exit status. */
|
/* Check the exit status. */
|
||||||
|
@ -1846,13 +1752,9 @@ void DerivationGoal::startBuilder()
|
||||||
uid. */
|
uid. */
|
||||||
buildUser.kill();
|
buildUser.kill();
|
||||||
|
|
||||||
/* Change ownership of the temporary build directory, if we're
|
/* Change ownership of the temporary build directory. */
|
||||||
root. If we're not root, then the setuid helper will do it
|
if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
|
||||||
just before it starts the builder. */
|
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
|
||||||
if (amPrivileged()) {
|
|
||||||
if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
|
|
||||||
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that the Nix store has the appropriate permissions,
|
/* Check that the Nix store has the appropriate permissions,
|
||||||
i.e., owned by root and mode 1775 (sticky bit on so that
|
i.e., owned by root and mode 1775 (sticky bit on so that
|
||||||
|
@ -2212,30 +2114,18 @@ void DerivationGoal::initChild()
|
||||||
if (buildUser.enabled()) {
|
if (buildUser.enabled()) {
|
||||||
printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser());
|
printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser());
|
||||||
|
|
||||||
if (amPrivileged()) {
|
if (setgroups(0, 0) == -1)
|
||||||
|
throw SysError("cannot clear the set of supplementary groups");
|
||||||
|
|
||||||
if (setgroups(0, 0) == -1)
|
if (setgid(buildUser.getGID()) == -1 ||
|
||||||
throw SysError("cannot clear the set of supplementary groups");
|
getgid() != buildUser.getGID() ||
|
||||||
|
getegid() != buildUser.getGID())
|
||||||
|
throw SysError("setgid failed");
|
||||||
|
|
||||||
if (setgid(buildUser.getGID()) == -1 ||
|
if (setuid(buildUser.getUID()) == -1 ||
|
||||||
getgid() != buildUser.getGID() ||
|
getuid() != buildUser.getUID() ||
|
||||||
getegid() != buildUser.getGID())
|
geteuid() != buildUser.getUID())
|
||||||
throw SysError("setgid failed");
|
throw SysError("setuid failed");
|
||||||
|
|
||||||
if (setuid(buildUser.getUID()) == -1 ||
|
|
||||||
getuid() != buildUser.getUID() ||
|
|
||||||
geteuid() != buildUser.getUID())
|
|
||||||
throw SysError("setuid failed");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/* Let the setuid helper take care of it. */
|
|
||||||
program = settings.nixLibexecDir + "/nix-setuid-helper";
|
|
||||||
args.push_back(program.c_str());
|
|
||||||
args.push_back("run-builder");
|
|
||||||
user = buildUser.getUser().c_str();
|
|
||||||
args.push_back(user.c_str());
|
|
||||||
args.push_back(drv.builder.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill in the arguments. */
|
/* Fill in the arguments. */
|
||||||
|
@ -2466,12 +2356,10 @@ void DerivationGoal::deleteTmpDir(bool force)
|
||||||
printMsg(lvlError,
|
printMsg(lvlError,
|
||||||
format("note: keeping build directory `%2%'")
|
format("note: keeping build directory `%2%'")
|
||||||
% drvPath % tmpDir);
|
% drvPath % tmpDir);
|
||||||
if (buildUser.enabled() && !amPrivileged())
|
|
||||||
getOwnership(tmpDir);
|
|
||||||
chmod(tmpDir.c_str(), 0755);
|
chmod(tmpDir.c_str(), 0755);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
deletePathWrapped(tmpDir);
|
deletePath(tmpDir);
|
||||||
tmpDir = "";
|
tmpDir = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2548,7 +2436,7 @@ Path DerivationGoal::addHashRewrite(const Path & path)
|
||||||
string h1 = string(path, settings.nixStore.size() + 1, 32);
|
string h1 = string(path, settings.nixStore.size() + 1, 32);
|
||||||
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
|
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
|
||||||
Path p = settings.nixStore + "/" + h2 + string(path, settings.nixStore.size() + 33);
|
Path p = settings.nixStore + "/" + h2 + string(path, settings.nixStore.size() + 33);
|
||||||
if (pathExists(p)) deletePathWrapped(p);
|
if (pathExists(p)) deletePath(p);
|
||||||
assert(path.size() == p.size());
|
assert(path.size() == p.size());
|
||||||
rewritesToTmp[h1] = h2;
|
rewritesToTmp[h1] = h2;
|
||||||
rewritesFromTmp[h2] = h1;
|
rewritesFromTmp[h2] = h1;
|
||||||
|
@ -2639,9 +2527,6 @@ SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool
|
||||||
|
|
||||||
SubstitutionGoal::~SubstitutionGoal()
|
SubstitutionGoal::~SubstitutionGoal()
|
||||||
{
|
{
|
||||||
/* !!! Once we let substitution goals run under a build user, we
|
|
||||||
need to use the setuid helper just as in ~DerivationGoal().
|
|
||||||
Idem for cancel. */
|
|
||||||
if (pid != -1) worker.childTerminated(pid);
|
if (pid != -1) worker.childTerminated(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2792,7 +2677,7 @@ void SubstitutionGoal::tryToRun()
|
||||||
|
|
||||||
/* Remove the (stale) output path if it exists. */
|
/* Remove the (stale) output path if it exists. */
|
||||||
if (pathExists(destPath))
|
if (pathExists(destPath))
|
||||||
deletePathWrapped(destPath);
|
deletePath(destPath);
|
||||||
|
|
||||||
worker.store.setSubstituterEnv();
|
worker.store.setSubstituterEnv();
|
||||||
|
|
||||||
|
|
|
@ -415,7 +415,7 @@ bool LocalStore::isActiveTempFile(const GCState & state,
|
||||||
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
void LocalStore::deleteGarbage(GCState & state, const Path & path)
|
||||||
{
|
{
|
||||||
unsigned long long bytesFreed;
|
unsigned long long bytesFreed;
|
||||||
deletePathWrapped(path, bytesFreed);
|
deletePath(path, bytesFreed);
|
||||||
state.results.bytesFreed += bytesFreed;
|
state.results.bytesFreed += bytesFreed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1328,7 +1328,7 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
|
||||||
|
|
||||||
if (repair || !isValidPath(dstPath)) {
|
if (repair || !isValidPath(dstPath)) {
|
||||||
|
|
||||||
if (pathExists(dstPath)) deletePathWrapped(dstPath);
|
if (pathExists(dstPath)) deletePath(dstPath);
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
StringSource source(dump);
|
StringSource source(dump);
|
||||||
|
@ -1397,7 +1397,7 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
|
||||||
|
|
||||||
if (repair || !isValidPath(dstPath)) {
|
if (repair || !isValidPath(dstPath)) {
|
||||||
|
|
||||||
if (pathExists(dstPath)) deletePathWrapped(dstPath);
|
if (pathExists(dstPath)) deletePath(dstPath);
|
||||||
|
|
||||||
writeFile(dstPath, s);
|
writeFile(dstPath, s);
|
||||||
|
|
||||||
|
@ -1630,7 +1630,7 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
|
||||||
|
|
||||||
if (!isValidPath(dstPath)) {
|
if (!isValidPath(dstPath)) {
|
||||||
|
|
||||||
if (pathExists(dstPath)) deletePathWrapped(dstPath);
|
if (pathExists(dstPath)) deletePath(dstPath);
|
||||||
|
|
||||||
if (rename(unpacked.c_str(), dstPath.c_str()) == -1)
|
if (rename(unpacked.c_str(), dstPath.c_str()) == -1)
|
||||||
throw SysError(format("cannot move `%1%' to `%2%'")
|
throw SysError(format("cannot move `%1%' to `%2%'")
|
||||||
|
|
|
@ -322,7 +322,7 @@ typedef set<Inode> InodesSeen;
|
||||||
- the permissions are set of 444 or 555 (i.e., read-only with or
|
- the permissions are set of 444 or 555 (i.e., read-only with or
|
||||||
without execute permission; setuid bits etc. are cleared)
|
without execute permission; setuid bits etc. are cleared)
|
||||||
- the owner and group are set to the Nix user and group, if we're
|
- the owner and group are set to the Nix user and group, if we're
|
||||||
in a setuid Nix installation. */
|
running as root. */
|
||||||
void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen);
|
void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen);
|
||||||
void canonicalisePathMetaData(const Path & path, uid_t fromUid);
|
void canonicalisePathMetaData(const Path & path, uid_t fromUid);
|
||||||
|
|
||||||
|
@ -330,16 +330,4 @@ void canonicaliseTimestampAndPermissions(const Path & path);
|
||||||
|
|
||||||
MakeError(PathInUse, Error);
|
MakeError(PathInUse, Error);
|
||||||
|
|
||||||
/* Whether we are root. */
|
|
||||||
bool amPrivileged();
|
|
||||||
|
|
||||||
/* Recursively change the ownership of `path' to the current uid. */
|
|
||||||
void getOwnership(const Path & path);
|
|
||||||
|
|
||||||
/* Like deletePath(), but changes the ownership of `path' using the
|
|
||||||
setuid wrapper if necessary (and possible). */
|
|
||||||
void deletePathWrapped(const Path & path, unsigned long long & bytesFreed);
|
|
||||||
|
|
||||||
void deletePathWrapped(const Path & path);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -968,19 +968,6 @@ void closeOnExec(int fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void setuidCleanup()
|
|
||||||
{
|
|
||||||
/* Don't trust the environment. */
|
|
||||||
environ = 0;
|
|
||||||
|
|
||||||
/* Make sure that file descriptors 0, 1, 2 are open. */
|
|
||||||
for (int fd = 0; fd <= 2; ++fd) {
|
|
||||||
struct stat st;
|
|
||||||
if (fstat(fd, &st) == -1) abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_VFORK
|
#if HAVE_VFORK
|
||||||
pid_t (*maybeVfork)() = vfork;
|
pid_t (*maybeVfork)() = vfork;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -268,10 +268,6 @@ void closeMostFDs(const set<int> & exceptions);
|
||||||
/* Set the close-on-exec flag for the given file descriptor. */
|
/* Set the close-on-exec flag for the given file descriptor. */
|
||||||
void closeOnExec(int fd);
|
void closeOnExec(int fd);
|
||||||
|
|
||||||
/* Common initialisation for setuid programs: clear the environment,
|
|
||||||
sanitize file handles 0, 1 and 2. */
|
|
||||||
void setuidCleanup();
|
|
||||||
|
|
||||||
/* Call vfork() if available, otherwise fork(). */
|
/* Call vfork() if available, otherwise fork(). */
|
||||||
extern pid_t (*maybeVfork)();
|
extern pid_t (*maybeVfork)();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
libexec_PROGRAMS = nix-setuid-helper
|
|
||||||
|
|
||||||
nix_setuid_helper_SOURCES = nix-setuid-helper.cc
|
|
||||||
nix_setuid_helper_LDADD = ../libutil/libutil.la \
|
|
||||||
../boost/format/libformat.la
|
|
||||||
|
|
||||||
AM_CXXFLAGS = -I$(srcdir)/.. -I$(srcdir)/../libutil
|
|
|
@ -1,263 +0,0 @@
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <grp.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util.hh"
|
|
||||||
|
|
||||||
using namespace nix;
|
|
||||||
|
|
||||||
|
|
||||||
extern char * * environ;
|
|
||||||
|
|
||||||
|
|
||||||
/* Recursively change the ownership of `path' to user `uidTo' and
|
|
||||||
group `gidTo'. `path' must currently be owned by user `uidFrom',
|
|
||||||
or, if `uidFrom' is -1, by group `gidFrom'. */
|
|
||||||
static void secureChown(uid_t uidFrom, gid_t gidFrom,
|
|
||||||
uid_t uidTo, gid_t gidTo, const Path & path)
|
|
||||||
{
|
|
||||||
format error = format("cannot change ownership of `%1%'") % path;
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
if (lstat(path.c_str(), &st) == -1)
|
|
||||||
/* Important: don't give any detailed error messages here.
|
|
||||||
Otherwise, the Nix account can discover information about
|
|
||||||
the existence of paths that it doesn't normally have access
|
|
||||||
to. */
|
|
||||||
throw Error(error);
|
|
||||||
|
|
||||||
if (uidFrom != (uid_t) -1) {
|
|
||||||
assert(uidFrom != 0);
|
|
||||||
if (st.st_uid != uidFrom)
|
|
||||||
throw Error(error);
|
|
||||||
} else {
|
|
||||||
assert(gidFrom != 0);
|
|
||||||
if (st.st_gid != gidFrom)
|
|
||||||
throw Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(uidTo != 0 && gidTo != 0);
|
|
||||||
|
|
||||||
#if HAVE_LCHOWN
|
|
||||||
if (lchown(path.c_str(), uidTo, gidTo) == -1)
|
|
||||||
throw Error(error);
|
|
||||||
#else
|
|
||||||
if (!S_ISLNK(st.st_mode) &&
|
|
||||||
chown(path.c_str(), uidTo, gidTo) == -1)
|
|
||||||
throw Error(error);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
|
||||||
Strings names = readDirectory(path);
|
|
||||||
for (Strings::iterator i = names.begin(); i != names.end(); ++i)
|
|
||||||
/* !!! recursion; check stack depth */
|
|
||||||
secureChown(uidFrom, gidFrom, uidTo, gidTo, path + "/" + *i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static uid_t nameToUid(const string & userName)
|
|
||||||
{
|
|
||||||
struct passwd * pw = getpwnam(userName.c_str());
|
|
||||||
if (!pw)
|
|
||||||
throw Error(format("user `%1%' does not exist") % userName);
|
|
||||||
return pw->pw_uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void checkIfBuildUser(const StringSet & buildUsers,
|
|
||||||
const string & userName)
|
|
||||||
{
|
|
||||||
if (buildUsers.find(userName) == buildUsers.end())
|
|
||||||
throw Error(format("user `%1%' is not a member of the build users group")
|
|
||||||
% userName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Run `program' under user account `targetUser'. `targetUser' should
|
|
||||||
be a member of `buildUsersGroup'. The ownership of the current
|
|
||||||
directory is changed from the Nix user (uidNix) to the target
|
|
||||||
user. */
|
|
||||||
static void runBuilder(uid_t uidNix, gid_t gidBuildUsers,
|
|
||||||
const StringSet & buildUsers, const string & targetUser,
|
|
||||||
string program, int argc, char * * argv, char * * env)
|
|
||||||
{
|
|
||||||
uid_t uidTargetUser = nameToUid(targetUser);
|
|
||||||
|
|
||||||
/* Sanity check. */
|
|
||||||
if (uidTargetUser == 0)
|
|
||||||
throw Error("won't setuid to root");
|
|
||||||
|
|
||||||
/* Verify that the target user is a member of the build users
|
|
||||||
group. */
|
|
||||||
checkIfBuildUser(buildUsers, targetUser);
|
|
||||||
|
|
||||||
/* Chown the current directory, *if* it is owned by the Nix
|
|
||||||
account. The idea is that the current directory is the
|
|
||||||
temporary build directory in /tmp or somewhere else, and we
|
|
||||||
don't want to create that directory here. */
|
|
||||||
secureChown(uidNix, (gid_t) -1, uidTargetUser, gidBuildUsers, ".");
|
|
||||||
|
|
||||||
/* Set the real, effective and saved gid. Must be done before
|
|
||||||
setuid(), otherwise it won't set the real and saved gids. */
|
|
||||||
if (setgroups(0, 0) == -1)
|
|
||||||
throw SysError("cannot clear the set of supplementary groups");
|
|
||||||
|
|
||||||
if (setgid(gidBuildUsers) == -1 ||
|
|
||||||
getgid() != gidBuildUsers ||
|
|
||||||
getegid() != gidBuildUsers)
|
|
||||||
throw SysError("setgid failed");
|
|
||||||
|
|
||||||
/* Set the real, effective and saved uid. */
|
|
||||||
if (setuid(uidTargetUser) == -1 ||
|
|
||||||
getuid() != uidTargetUser ||
|
|
||||||
geteuid() != uidTargetUser)
|
|
||||||
throw SysError("setuid failed");
|
|
||||||
|
|
||||||
/* Execute the program. */
|
|
||||||
std::vector<const char *> args;
|
|
||||||
for (int i = 0; i < argc; ++i)
|
|
||||||
args.push_back(argv[i]);
|
|
||||||
args.push_back(0);
|
|
||||||
|
|
||||||
environ = env;
|
|
||||||
|
|
||||||
/* Glibc clears TMPDIR in setuid programs (see
|
|
||||||
sysdeps/generic/unsecvars.h in the Glibc sources), so bring it
|
|
||||||
back. */
|
|
||||||
setenv("TMPDIR", getenv("NIX_BUILD_TOP"), 1);
|
|
||||||
|
|
||||||
if (execv(program.c_str(), (char * *) &args[0]) == -1)
|
|
||||||
throw SysError(format("cannot execute `%1%'") % program);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void killBuildUser(gid_t gidBuildUsers,
|
|
||||||
const StringSet & buildUsers, const string & userName)
|
|
||||||
{
|
|
||||||
uid_t uid = nameToUid(userName);
|
|
||||||
|
|
||||||
/* Verify that the user whose processes we are to kill is a member
|
|
||||||
of the build users group. */
|
|
||||||
checkIfBuildUser(buildUsers, userName);
|
|
||||||
|
|
||||||
assert(uid != 0);
|
|
||||||
|
|
||||||
killUser(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef NIX_SETUID_CONFIG_FILE
|
|
||||||
#define NIX_SETUID_CONFIG_FILE "/etc/nix-setuid.conf"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static void run(int argc, char * * argv)
|
|
||||||
{
|
|
||||||
char * * oldEnviron = environ;
|
|
||||||
|
|
||||||
setuidCleanup();
|
|
||||||
|
|
||||||
if (geteuid() != 0)
|
|
||||||
throw Error("nix-setuid-wrapper must be setuid root");
|
|
||||||
|
|
||||||
|
|
||||||
/* Read the configuration file. It should consist of two words:
|
|
||||||
|
|
||||||
<nix-user-name> <nix-builders-group>
|
|
||||||
|
|
||||||
The first is the privileged account under which the main Nix
|
|
||||||
processes run (i.e., the supposed caller). It should match our
|
|
||||||
real uid. The second is the Unix group to which the Nix
|
|
||||||
builders belong (and nothing else!). */
|
|
||||||
string configFile = NIX_SETUID_CONFIG_FILE;
|
|
||||||
AutoCloseFD fdConfig = open(configFile.c_str(), O_RDONLY);
|
|
||||||
if (fdConfig == -1)
|
|
||||||
throw SysError(format("opening `%1%'") % configFile);
|
|
||||||
|
|
||||||
/* Config file should be owned by root. */
|
|
||||||
struct stat st;
|
|
||||||
if (fstat(fdConfig, &st) == -1) throw SysError("statting file");
|
|
||||||
if (st.st_uid != 0)
|
|
||||||
throw Error(format("`%1%' not owned by root") % configFile);
|
|
||||||
if (st.st_mode & (S_IWGRP | S_IWOTH))
|
|
||||||
throw Error(format("`%1%' should not be group or world-writable") % configFile);
|
|
||||||
|
|
||||||
vector<string> tokens = tokenizeString<vector<string> >(readFile(fdConfig));
|
|
||||||
|
|
||||||
fdConfig.close();
|
|
||||||
|
|
||||||
if (tokens.size() != 2)
|
|
||||||
throw Error(format("parse error in `%1%'") % configFile);
|
|
||||||
|
|
||||||
string nixUser = tokens[0];
|
|
||||||
string buildUsersGroup = tokens[1];
|
|
||||||
|
|
||||||
|
|
||||||
/* Check that the caller (real uid) is the one allowed to call
|
|
||||||
this program. */
|
|
||||||
uid_t uidNix = nameToUid(nixUser);
|
|
||||||
if (uidNix != getuid())
|
|
||||||
throw Error("you are not allowed to call this program, go away");
|
|
||||||
|
|
||||||
|
|
||||||
/* Get the gid and members of buildUsersGroup. */
|
|
||||||
struct group * gr = getgrnam(buildUsersGroup.c_str());
|
|
||||||
if (!gr)
|
|
||||||
throw Error(format("group `%1%' does not exist") % buildUsersGroup);
|
|
||||||
gid_t gidBuildUsers = gr->gr_gid;
|
|
||||||
|
|
||||||
StringSet buildUsers;
|
|
||||||
for (char * * p = gr->gr_mem; *p; ++p)
|
|
||||||
buildUsers.insert(*p);
|
|
||||||
|
|
||||||
|
|
||||||
/* Perform the desired command. */
|
|
||||||
if (argc < 2)
|
|
||||||
throw Error("invalid arguments");
|
|
||||||
|
|
||||||
string command(argv[1]);
|
|
||||||
|
|
||||||
if (command == "run-builder") {
|
|
||||||
/* Syntax: nix-setuid-helper run-builder <username> <program>
|
|
||||||
<arg0 arg1...> */
|
|
||||||
if (argc < 4) throw Error("missing user name / program name");
|
|
||||||
runBuilder(uidNix, gidBuildUsers, buildUsers,
|
|
||||||
argv[2], argv[3], argc - 4, argv + 4, oldEnviron);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (command == "get-ownership") {
|
|
||||||
/* Syntax: nix-setuid-helper get-ownership <path> */
|
|
||||||
if (argc != 3) throw Error("missing path");
|
|
||||||
secureChown((uid_t) -1, gidBuildUsers, uidNix, gidBuildUsers, argv[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (command == "kill") {
|
|
||||||
/* Syntax: nix-setuid-helper kill <username> */
|
|
||||||
if (argc != 3) throw Error("missing user name");
|
|
||||||
killBuildUser(gidBuildUsers, buildUsers, argv[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else throw Error ("invalid command");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char * * argv)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
run(argc, argv);
|
|
||||||
} catch (Error & e) {
|
|
||||||
std::cerr << e.msg() << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue