forked from lix-project/lix
* Change the ownership of store paths to the Nix account before
deleting them using the setuid helper.
This commit is contained in:
parent
7d8cf316ee
commit
6a07ff1ec0
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 (uidFrom != -1) {
|
||||||
|
assert(uidFrom != 0);
|
||||||
if (st.st_uid != uidFrom)
|
if (st.st_uid != uidFrom)
|
||||||
throw Error(format("path `%1%' owned by the wrong owner") % path);
|
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 {
|
||||||
|
|
Loading…
Reference in a new issue