diff --git a/src/libstore/build.cc b/src/libstore/build.cc index e75215d2c4d1..af1d14b1c6be 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -2193,6 +2193,10 @@ void DerivationGoal::registerOutputs() ValidPathInfos infos; + /* Inherit ownership of outputs from the permission of the derivation + file. */ + SecretMode smode(getOwnerOfSecretFile(drvPath)); + /* Check whether the output paths were created, and grep each output path to determine what other paths it references. Also make all output paths read-only. */ @@ -2200,9 +2204,6 @@ void DerivationGoal::registerOutputs() Path path = i->second.path; if (missingPaths.find(path) == missingPaths.end()) continue; - // :TODO: Transfer the user name down to here. - SecretMode smode(publicUserName()); - Path actualPath = path; if (useChroot) { actualPath = chrootRootDir + path; diff --git a/src/libutil/util.cc b/src/libutil/util.cc index c1669f3a21ea..093c014185c6 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -1174,6 +1175,51 @@ const string & getCurrentUserName() } +string getOwnerOfSecretFile(const Path & path) +{ + /* Extract the uid of the file */ + struct stat st; + if (stat(path.c_str(), &st) == -1) + throw SysError("statting file"); + + /* If the file is readable by the group, then this means this is a + public file. */ + if (st.st_mode & S_IRGRP) + return publicUserName(); + + /* Map the uid of the private file to the user name */ + struct passwd pwd; + struct passwd *ppwd; + char *buf; + size_t bufsize; + int err; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize == size_t(-1)) + bufsize = 16 * 1024; + + buf = new char[bufsize]; + if (!buf) + throw SysError("allocating passwd entry"); + + err = getpwuid_r(st.st_uid, &pwd, buf, bufsize, &ppwd); + if (!ppwd) { + delete[] buf; + if (err) + throw SysError("finding owner of a file"); + + /* The uid does not match any user name, no need to keep any secret + for a non-existing user name. */ + return publicUserName(); + } + + string userName(pwd.pw_name); + delete[] buf; + + return userName; +} + + SecretMode::SecretMode(const string & userName) : userName_(userName), filterMask_(0), diff --git a/src/libutil/util.hh b/src/libutil/util.hh index fb09fcc156fd..196cd2e27b80 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -391,6 +391,10 @@ const string & publicUserName(); const string & getCurrentUserName(); +/* Returns the owner of a file if the file is secret */ +string getOwnerOfSecretFile(const Path & path); + + /* Ensure that files created within this scope are secure by default. This RAI does not protect against doing a chmod of the file. */ class SecretMode diff --git a/tests/sec-secretDerivation.sh b/tests/sec-secretDerivation.sh index 05acfa07a803..803065600ef0 100644 --- a/tests/sec-secretDerivation.sh +++ b/tests/sec-secretDerivation.sh @@ -34,8 +34,32 @@ if ! test $publicDrvStat = $expected; then exit 1 fi +expected="dr-xr-xr-x" +if ! test $publicOutStat = $expected; then + echo "Public derivation output director is not only world readbale." + exit 1 +fi + +expected="-r--r--r--" +if ! test $publicOutFileStat = $expected; then + echo "Public derivation output file is not only world readbale." + exit 1 +fi + expected="-r--------" if ! test $secretDrvStat = $expected; then echo "Secret derivation is not only root readbale." exit 1 fi + +expected="dr-x------" +if ! test $secretOutStat = $expected; then + echo "Secret derivation output directory is not only root readbale." + exit 1 +fi + +expected="-r--------" +if ! test $secretOutFileStat = $expected; then + echo "Secret derivation output file is not only root readbale." + exit 1 +fi