1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-07 01:51:47 +02:00

Remove nix-setuid-helper

AFAIK, nobody uses it, it's not maintained, and it has no tests.
This commit is contained in:
Eelco Dolstra 2013-11-14 11:57:37 +01:00
parent 89e6781cc5
commit a478e8a7bb
13 changed files with 48 additions and 524 deletions

View file

@ -1,3 +1,3 @@
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

View file

@ -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()
{
assert(enabled());
if (amPrivileged())
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);
killUser(uid);
}
@ -971,15 +888,11 @@ void DerivationGoal::killChild()
worker.childTerminated(pid);
if (buildUser.enabled()) {
/* We can't use pid.kill(), since we may not have the
appropriate privilege. I.e., if we're not root, then
setuid helper should do it).
Also, if we're using a build user, then there is a
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
/* If we're using a build user, then there is a 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. */
::kill(-pid, SIGKILL); /* ignore the result */
buildUser.kill();
@ -1349,7 +1262,7 @@ void DerivationGoal::tryToBuild()
if (worker.store.isValidPath(path)) continue;
if (!pathExists(path)) continue;
debug(format("removing unregistered path `%1%'") % path);
deletePathWrapped(path);
deletePath(path);
}
/* 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)
throw SysError(format("moving `%1%' to `%2%'") % tmpPath % storePath);
if (pathExists(oldPath))
deletePathWrapped(oldPath);
deletePath(oldPath);
}
@ -1532,13 +1445,6 @@ void DerivationGoal::buildDone()
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. */
@ -1846,13 +1752,9 @@ void DerivationGoal::startBuilder()
uid. */
buildUser.kill();
/* Change ownership of the temporary build directory, if we're
root. If we're not root, then the setuid helper will do it
just before it starts the builder. */
if (amPrivileged()) {
if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1)
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
}
/* Change ownership of the temporary build directory. */
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,
i.e., owned by root and mode 1775 (sticky bit on so that
@ -2212,30 +2114,18 @@ void DerivationGoal::initChild()
if (buildUser.enabled()) {
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)
throw SysError("cannot clear the set of supplementary groups");
if (setgid(buildUser.getGID()) == -1 ||
getgid() != buildUser.getGID() ||
getegid() != buildUser.getGID())
throw SysError("setgid failed");
if (setgid(buildUser.getGID()) == -1 ||
getgid() != buildUser.getGID() ||
getegid() != buildUser.getGID())
throw SysError("setgid 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());
}
if (setuid(buildUser.getUID()) == -1 ||
getuid() != buildUser.getUID() ||
geteuid() != buildUser.getUID())
throw SysError("setuid failed");
}
/* Fill in the arguments. */
@ -2466,12 +2356,10 @@ void DerivationGoal::deleteTmpDir(bool force)
printMsg(lvlError,
format("note: keeping build directory `%2%'")
% drvPath % tmpDir);
if (buildUser.enabled() && !amPrivileged())
getOwnership(tmpDir);
chmod(tmpDir.c_str(), 0755);
}
else
deletePathWrapped(tmpDir);
deletePath(tmpDir);
tmpDir = "";
}
}
@ -2548,7 +2436,7 @@ Path DerivationGoal::addHashRewrite(const Path & path)
string h1 = string(path, settings.nixStore.size() + 1, 32);
string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
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());
rewritesToTmp[h1] = h2;
rewritesFromTmp[h2] = h1;
@ -2639,9 +2527,6 @@ SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool
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);
}
@ -2792,7 +2677,7 @@ void SubstitutionGoal::tryToRun()
/* Remove the (stale) output path if it exists. */
if (pathExists(destPath))
deletePathWrapped(destPath);
deletePath(destPath);
worker.store.setSubstituterEnv();

View file

@ -415,7 +415,7 @@ bool LocalStore::isActiveTempFile(const GCState & state,
void LocalStore::deleteGarbage(GCState & state, const Path & path)
{
unsigned long long bytesFreed;
deletePathWrapped(path, bytesFreed);
deletePath(path, bytesFreed);
state.results.bytesFreed += bytesFreed;
}

View file

@ -1328,7 +1328,7 @@ Path LocalStore::addToStoreFromDump(const string & dump, const string & name,
if (repair || !isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
if (pathExists(dstPath)) deletePath(dstPath);
if (recursive) {
StringSource source(dump);
@ -1397,7 +1397,7 @@ Path LocalStore::addTextToStore(const string & name, const string & s,
if (repair || !isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
if (pathExists(dstPath)) deletePath(dstPath);
writeFile(dstPath, s);
@ -1630,7 +1630,7 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
if (!isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
if (pathExists(dstPath)) deletePath(dstPath);
if (rename(unpacked.c_str(), dstPath.c_str()) == -1)
throw SysError(format("cannot move `%1%' to `%2%'")

View file

@ -322,7 +322,7 @@ typedef set<Inode> InodesSeen;
- the permissions are set of 444 or 555 (i.e., read-only with or
without execute permission; setuid bits etc. are cleared)
- 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);
@ -330,16 +330,4 @@ void canonicaliseTimestampAndPermissions(const Path & path);
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);
}

View file

@ -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
pid_t (*maybeVfork)() = vfork;
#else

View file

@ -268,10 +268,6 @@ void closeMostFDs(const set<int> & exceptions);
/* Set the close-on-exec flag for the given file descriptor. */
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(). */
extern pid_t (*maybeVfork)();

View file

@ -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

View file

@ -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;
}
}