* Change the ownership of store paths to the Nix account before

deleting them using the setuid helper.
This commit is contained in:
Eelco Dolstra 2006-12-07 14:14:35 +00:00
parent 7d8cf316ee
commit 6a07ff1ec0
3 changed files with 104 additions and 38 deletions

View file

@ -458,7 +458,7 @@ static bool amPrivileged()
} }
void killUserWrapped(uid_t uid) static void killUserWrapped(uid_t uid)
{ {
if (amPrivileged()) if (amPrivileged())
killUser(uid); killUser(uid);
@ -468,6 +468,58 @@ void killUserWrapped(uid_t uid)
} }
static void getOwnership(const Path & path)
{
string program = 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("get-ownership");
args.push_back(path.c_str());
args.push_back(0);
execve(program.c_str(), (char * *) &args[0], 0);
throw SysError(format("executing `%1%'") % program);
}
catch (std::exception & e) {
std::cerr << "error: " << e.what() << std::endl;
}
quickExit(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));
}
static void deletePathWrapped(const Path & path)
{
/* When using build users and we're not root, we may not have
sufficient permission to delete the path. So use the setuid
helper to change ownership to us. */
if (querySetting("build-users-group", "") != ""
|| !amPrivileged())
getOwnership(path);
deletePath(path);
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -1170,7 +1222,7 @@ void DerivationGoal::startBuilder()
throw Error(format("obstructed build: path `%1%' exists") % path); throw Error(format("obstructed build: path `%1%' exists") % path);
if (pathExists(path)) { if (pathExists(path)) {
debug(format("removing unregistered path `%1%'") % path); debug(format("removing unregistered path `%1%'") % path);
deletePath(path); deletePathWrapped(path);
} }
} }
@ -1619,7 +1671,7 @@ void DerivationGoal::deleteTmpDir(bool force)
format("builder for `%1%' failed; keeping build directory `%2%'") format("builder for `%1%' failed; keeping build directory `%2%'")
% drvPath % tmpDir); % drvPath % tmpDir);
else else
deletePath(tmpDir); deletePathWrapped(tmpDir);
tmpDir = ""; tmpDir = "";
} }
} }
@ -1833,7 +1885,7 @@ void SubstitutionGoal::tryToRun()
/* Remove the (stale) output path if it exists. */ /* Remove the (stale) output path if it exists. */
if (pathExists(storePath)) if (pathExists(storePath))
deletePath(storePath); deletePathWrapped(storePath);
/* Fork the substitute program. */ /* Fork the substitute program. */
pid = fork(); pid = fork();

View file

@ -797,7 +797,7 @@ string runProgram(Path program)
/* Wait for the child to finish. */ /* Wait for the child to finish. */
int status = pid.wait(true); int status = pid.wait(true);
if (!statusOk(status)) if (!statusOk(status))
throw Error(format("program `%1% %2%") throw Error(format("program `%1%' %2%")
% program % statusToString(status)); % program % statusToString(status));
return result; return result;

View file

@ -17,27 +17,42 @@
using namespace nix; using namespace nix;
/* Recursively change the ownership of `path' from `uidFrom' to /* Recursively change the ownership of `path' to user `uidTo' and
`uidTo' and `gidTo'. Barf if we encounter a file not owned by group `gidTo'. `path' must currently be owned by user `uidFrom',
`uidFrom'. */ or, if `uidFrom' is -1, by group `gidFrom'. */
static void secureChown(uid_t uidFrom, uid_t uidTo, gid_t gidTo, static void secureChown(uid_t uidFrom, gid_t gidFrom,
const Path & path) uid_t uidTo, gid_t gidTo, const Path & path)
{ {
format error = format("cannot change ownership of `%1%'") % path;
struct stat st; struct stat st;
if (lstat(path.c_str(), &st) == -1) if (lstat(path.c_str(), &st) == -1)
throw SysError(format("statting of `%1%'") % path); /* 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 (st.st_uid != uidFrom) if (uidFrom != -1) {
throw Error(format("path `%1%' owned by the wrong owner") % path); 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 (lchown(path.c_str(), uidTo, gidTo) == -1) if (lchown(path.c_str(), uidTo, gidTo) == -1)
throw SysError(format("changing ownership of `%1%'") % path); throw Error(error);
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
Strings names = readDirectory(path); Strings names = readDirectory(path);
for (Strings::iterator i = names.begin(); i != names.end(); ++i) for (Strings::iterator i = names.begin(); i != names.end(); ++i)
/* !!! recursion; check stack depth */ /* !!! recursion; check stack depth */
secureChown(uidFrom, uidTo, gidTo, path + "/" + *i); secureChown(uidFrom, gidFrom, uidTo, gidTo, path + "/" + *i);
} }
} }
@ -55,8 +70,8 @@ static uid_t nameToUid(const string & userName)
be a member of `buildUsersGroup'. The ownership of the current be a member of `buildUsersGroup'. The ownership of the current
directory is changed from the Nix user (uidNix) to the target directory is changed from the Nix user (uidNix) to the target
user. */ user. */
static void runBuilder(uid_t uidNix, static void runBuilder(uid_t uidNix, gid_t gidBuildUsers,
const string & buildUsersGroup, const string & targetUser, const StringSet & buildUsers, const string & targetUser,
string program, int argc, char * * argv, char * * env) string program, int argc, char * * argv, char * * env)
{ {
uid_t uidTargetUser = nameToUid(targetUser); uid_t uidTargetUser = nameToUid(targetUser);
@ -65,29 +80,16 @@ static void runBuilder(uid_t uidNix,
if (uidTargetUser == 0) if (uidTargetUser == 0)
throw Error("won't setuid to root"); throw Error("won't setuid to root");
/* 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;
/* Verify that the target user is a member of that group. */ /* Verify that the target user is a member of that group. */
Strings users; if (buildUsers.find(targetUser) == buildUsers.end())
bool found = false; throw Error(format("user `%1%' is not a member of the build users group")
for (char * * p = gr->gr_mem; *p; ++p) % targetUser);
if (string(*p) == targetUser) {
found = true;
break;
}
if (!found)
throw Error(format("user `%1%' is not a member of `%2%'")
% targetUser % buildUsersGroup);
/* Chown the current directory, *if* it is owned by the Nix /* Chown the current directory, *if* it is owned by the Nix
account. The idea is that the current directory is the account. The idea is that the current directory is the
temporary build directory in /tmp or somewhere else, and we temporary build directory in /tmp or somewhere else, and we
don't want to create that directory here. */ don't want to create that directory here. */
secureChown(uidNix, uidTargetUser, gidBuildUsers, "."); secureChown(uidNix, -1, uidTargetUser, gidBuildUsers, ".");
/* Set the real, effective and saved gid. Must be done before /* Set the real, effective and saved gid. Must be done before
setuid(), otherwise it won't set the real and saved gids. */ setuid(), otherwise it won't set the real and saved gids. */
@ -171,6 +173,17 @@ static void run(int argc, char * * argv)
throw Error("you are not allowed to call this program, go away"); 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. */ /* Perform the desired command. */
if (argc < 2) if (argc < 2)
throw Error("invalid arguments"); throw Error("invalid arguments");
@ -181,19 +194,20 @@ static void run(int argc, char * * argv)
/* Syntax: nix-setuid-helper run-builder <username> <program> /* Syntax: nix-setuid-helper run-builder <username> <program>
<arg0 arg1...> */ <arg0 arg1...> */
if (argc < 4) throw Error("missing user name / program name"); if (argc < 4) throw Error("missing user name / program name");
runBuilder(uidNix, buildUsersGroup, runBuilder(uidNix, gidBuildUsers, buildUsers,
argv[2], argv[3], argc - 4, argv + 4, oldEnviron); argv[2], argv[3], argc - 4, argv + 4, oldEnviron);
} }
else if (command == "fix-ownership") { else if (command == "get-ownership") {
/* Syntax: nix-setuid-helper <fix-ownership> <path> */ /* Syntax: nix-setuid-helper get-ownership <path> */
if (argc != 3) throw Error("missing path");
secureChown(-1, gidBuildUsers, uidNix, gidBuildUsers, argv[2]);
} }
else throw Error ("invalid command"); else throw Error ("invalid command");
} }
int main(int argc, char * * argv) int main(int argc, char * * argv)
{ {
try { try {