X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/5a3264396f9b167fa99fb6e375ae8b978d829a39..dca07c4d676ac7bbc3543dff359735c0275e90fd:/apt-pkg/contrib/fileutl.cc diff --git a/apt-pkg/contrib/fileutl.cc b/apt-pkg/contrib/fileutl.cc index 1be782bac..014f0ee51 100644 --- a/apt-pkg/contrib/fileutl.cc +++ b/apt-pkg/contrib/fileutl.cc @@ -158,24 +158,33 @@ bool CopyFile(FileFd &From,FileFd &To) if (From.IsOpen() == false || To.IsOpen() == false || From.Failed() == true || To.Failed() == true) return false; - + // Buffered copy between fds std::unique_ptr Buf(new unsigned char[64000]); - unsigned long long Size = From.Size(); - while (Size != 0) - { - unsigned long long ToRead = Size; - if (Size > 64000) - ToRead = 64000; - - if (From.Read(Buf.get(),ToRead) == false || + constexpr unsigned long long BufSize = sizeof(Buf.get())/sizeof(Buf.get()[0]); + unsigned long long ToRead = 0; + do { + if (From.Read(Buf.get(),BufSize, &ToRead) == false || To.Write(Buf.get(),ToRead) == false) return false; - - Size -= ToRead; - } + } while (ToRead != 0); + + return true; +} + /*}}}*/ +bool RemoveFile(char const * const Function, std::string const &FileName)/*{{{*/ +{ + if (FileName == "/dev/null") + return true; + errno = 0; + if (unlink(FileName.c_str()) != 0) + { + if (errno == ENOENT) + return true; - return true; + return _error->WarningE(Function,_("Problem unlinking the file %s"), FileName.c_str()); + } + return true; } /*}}}*/ // GetLock - Gets a lock file /*{{{*/ @@ -671,7 +680,7 @@ string flAbsPath(string File) char *p = realpath(File.c_str(), NULL); if (p == NULL) { - _error->Errno("realpath", "flAbsPath failed"); + _error->Errno("realpath", "flAbsPath on %s failed", File.c_str()); return ""; } std::string AbsPath(p); @@ -1130,24 +1139,28 @@ bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Co if ((Mode & ReadWrite) == 0) return FileFdError("No openmode provided in FileFd::Open for %s", FileName.c_str()); - if ((Mode & Atomic) == Atomic) + unsigned int OpenMode = Mode; + if (FileName == "/dev/null") + OpenMode = OpenMode & ~(Atomic | Exclusive | Create | Empty); + + if ((OpenMode & Atomic) == Atomic) { Flags |= Replace; } - else if ((Mode & (Exclusive | Create)) == (Exclusive | Create)) + else if ((OpenMode & (Exclusive | Create)) == (Exclusive | Create)) { // for atomic, this will be done by rename in Close() - unlink(FileName.c_str()); + RemoveFile("FileFd::Open", FileName); } - if ((Mode & Empty) == Empty) + if ((OpenMode & Empty) == Empty) { struct stat Buf; if (lstat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode)) - unlink(FileName.c_str()); + RemoveFile("FileFd::Open", FileName); } int fileflags = 0; - #define if_FLAGGED_SET(FLAG, MODE) if ((Mode & FLAG) == FLAG) fileflags |= MODE + #define if_FLAGGED_SET(FLAG, MODE) if ((OpenMode & FLAG) == FLAG) fileflags |= MODE if_FLAGGED_SET(ReadWrite, O_RDWR); else if_FLAGGED_SET(ReadOnly, O_RDONLY); else if_FLAGGED_SET(WriteOnly, O_WRONLY); @@ -1157,7 +1170,7 @@ bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Co if_FLAGGED_SET(Exclusive, O_EXCL); #undef if_FLAGGED_SET - if ((Mode & Atomic) == Atomic) + if ((OpenMode & Atomic) == Atomic) { char *name = strdup((FileName + ".XXXXXX").c_str()); @@ -1184,7 +1197,7 @@ bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Co iFd = open(FileName.c_str(), fileflags, AccessMode); this->FileName = FileName; - if (iFd == -1 || OpenInternDescriptor(Mode, compressor) == false) + if (iFd == -1 || OpenInternDescriptor(OpenMode, compressor) == false) { if (iFd != -1) { @@ -2006,12 +2019,13 @@ bool FileFd::Close() { if ((Flags & Compressed) != Compressed && iFd > 0 && close(iFd) != 0) Res &= _error->Errno("close",_("Problem closing the file %s"), FileName.c_str()); - if (d != NULL) - { - Res &= d->CloseDown(FileName); - delete d; - d = NULL; - } + } + + if (d != NULL) + { + Res &= d->CloseDown(FileName); + delete d; + d = NULL; } if ((Flags & Replace) == Replace) { @@ -2026,8 +2040,7 @@ bool FileFd::Close() if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail && FileName.empty() == false) - if (unlink(FileName.c_str()) != 0) - Res &= _error->WarningE("unlnk",_("Problem unlinking the file %s"), FileName.c_str()); + Res &= RemoveFile("FileFd::Close", FileName); if (Res == false) Flags |= Fail; @@ -2077,7 +2090,7 @@ bool FileFd::FileFdError(const char *Description,...) { } /*}}}*/ -APT_DEPRECATED gzFile FileFd::gzFd() { +gzFile FileFd::gzFd() { #ifdef HAVE_ZLIB return d->gz; #else @@ -2122,12 +2135,39 @@ std::string GetTempDir() /*{{{*/ struct stat st; if (!tmpdir || strlen(tmpdir) == 0 || // tmpdir is set - stat(tmpdir, &st) != 0 || (st.st_mode & S_IFDIR) == 0 || // exists and is directory - access(tmpdir, R_OK | W_OK | X_OK) != 0 // current user has rwx access to directory - ) + stat(tmpdir, &st) != 0 || (st.st_mode & S_IFDIR) == 0) // exists and is directory + tmpdir = "/tmp"; + else if (geteuid() != 0 && // root can do everything anyway + faccessat(-1, tmpdir, R_OK | W_OK | X_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW) != 0) // current user has rwx access to directory tmpdir = "/tmp"; return string(tmpdir); +} +std::string GetTempDir(std::string const &User) +{ + // no need/possibility to drop privs + if(getuid() != 0 || User.empty() || User == "root") + return GetTempDir(); + + struct passwd const * const pw = getpwnam(User.c_str()); + if (pw == NULL) + return GetTempDir(); + + gid_t const old_euid = geteuid(); + gid_t const old_egid = getegid(); + if (setegid(pw->pw_gid) != 0) + _error->Errno("setegid", "setegid %u failed", pw->pw_gid); + if (seteuid(pw->pw_uid) != 0) + _error->Errno("seteuid", "seteuid %u failed", pw->pw_uid); + + std::string const tmp = GetTempDir(); + + if (seteuid(old_euid) != 0) + _error->Errno("seteuid", "seteuid %u failed", old_euid); + if (setegid(old_egid) != 0) + _error->Errno("setegid", "setegid %u failed", old_egid); + + return tmp; } /*}}}*/ FileFd* GetTempFile(std::string const &Prefix, bool ImmediateUnlink, FileFd * const TmpFd) /*{{{*/ @@ -2208,11 +2248,14 @@ bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode)/ { close(Pipe[1]); fd = Pipe[0]; - } else if(Mode == FileFd::WriteOnly) + } + else if(Mode == FileFd::WriteOnly) { close(Pipe[0]); fd = Pipe[1]; } + else + return _error->Error("Popen supports ReadOnly (x)or WriteOnly mode only"); Fd.OpenDescriptor(fd, Mode, FileFd::None, true); return true; @@ -2237,9 +2280,15 @@ bool DropPrivileges() /*{{{*/ // empty setting disables privilege dropping - this also ensures // backward compatibility, see bug #764506 const std::string toUser = _config->Find("APT::Sandbox::User"); - if (toUser.empty()) + if (toUser.empty() || toUser == "root") return true; + // a lot can go wrong trying to drop privileges completely, + // so ideally we would like to verify that we have done it – + // but the verify asks for too much in case of fakeroot (and alike) + // [Specific checks can be overridden with dedicated options] + bool const VerifySandboxing = _config->FindB("APT::Sandbox::Verify", false); + // uid will be 0 in the end, but gid might be different anyway uid_t const old_uid = getuid(); gid_t const old_gid = getgid(); @@ -2279,51 +2328,68 @@ bool DropPrivileges() /*{{{*/ return _error->Errno("seteuid", "Failed to seteuid"); #endif - // Verify that the user has only a single group, and the correct one - gid_t groups[1]; - if (getgroups(1, groups) != 1) - return _error->Errno("getgroups", "Could not get new groups"); - if (groups[0] != pw->pw_gid) - return _error->Error("Could not switch group"); - - // Verify that gid, egid, uid, and euid changed - if (getgid() != pw->pw_gid) - return _error->Error("Could not switch group"); - if (getegid() != pw->pw_gid) - return _error->Error("Could not switch effective group"); - if (getuid() != pw->pw_uid) - return _error->Error("Could not switch user"); - if (geteuid() != pw->pw_uid) - return _error->Error("Could not switch effective user"); + // disabled by default as fakeroot doesn't implement getgroups currently (#806521) + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::Groups", false) == true) + { + // Verify that the user isn't still in any supplementary groups + long const ngroups_max = sysconf(_SC_NGROUPS_MAX); + std::unique_ptr gidlist(new gid_t[ngroups_max]); + if (unlikely(gidlist == NULL)) + return _error->Error("Allocation of a list of size %lu for getgroups failed", ngroups_max); + ssize_t gidlist_nr; + if ((gidlist_nr = getgroups(ngroups_max, gidlist.get())) < 0) + return _error->Errno("getgroups", "Could not get new groups (%lu)", ngroups_max); + for (ssize_t i = 0; i < gidlist_nr; ++i) + if (gidlist[i] != pw->pw_gid) + return _error->Error("Could not switch group, user %s is still in group %d", toUser.c_str(), gidlist[i]); + } + + // enabled by default as all fakeroot-lookalikes should fake that accordingly + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::IDs", true) == true) + { + // Verify that gid, egid, uid, and euid changed + if (getgid() != pw->pw_gid) + return _error->Error("Could not switch group"); + if (getegid() != pw->pw_gid) + return _error->Error("Could not switch effective group"); + if (getuid() != pw->pw_uid) + return _error->Error("Could not switch user"); + if (geteuid() != pw->pw_uid) + return _error->Error("Could not switch effective user"); #ifdef HAVE_GETRESUID - // verify that the saved set-user-id was changed as well - uid_t ruid = 0; - uid_t euid = 0; - uid_t suid = 0; - if (getresuid(&ruid, &euid, &suid)) - return _error->Errno("getresuid", "Could not get saved set-user-ID"); - if (suid != pw->pw_uid) - return _error->Error("Could not switch saved set-user-ID"); + // verify that the saved set-user-id was changed as well + uid_t ruid = 0; + uid_t euid = 0; + uid_t suid = 0; + if (getresuid(&ruid, &euid, &suid)) + return _error->Errno("getresuid", "Could not get saved set-user-ID"); + if (suid != pw->pw_uid) + return _error->Error("Could not switch saved set-user-ID"); #endif #ifdef HAVE_GETRESGID - // verify that the saved set-group-id was changed as well - gid_t rgid = 0; - gid_t egid = 0; - gid_t sgid = 0; - if (getresgid(&rgid, &egid, &sgid)) - return _error->Errno("getresuid", "Could not get saved set-group-ID"); - if (sgid != pw->pw_gid) - return _error->Error("Could not switch saved set-group-ID"); + // verify that the saved set-group-id was changed as well + gid_t rgid = 0; + gid_t egid = 0; + gid_t sgid = 0; + if (getresgid(&rgid, &egid, &sgid)) + return _error->Errno("getresuid", "Could not get saved set-group-ID"); + if (sgid != pw->pw_gid) + return _error->Error("Could not switch saved set-group-ID"); #endif + } - // Check that uid and gid changes do not work anymore - if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1)) - return _error->Error("Could restore a gid to root, privilege dropping did not work"); + // disabled as fakeroot doesn't forbid (by design) (re)gaining root from unprivileged + if (VerifySandboxing == true || _config->FindB("APT::Sandbox::Verify::Regain", false) == true) + { + // Check that uid and gid changes do not work anymore + if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1)) + return _error->Error("Could restore a gid to root, privilege dropping did not work"); - if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) - return _error->Error("Could restore a uid to root, privilege dropping did not work"); + if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) + return _error->Error("Could restore a uid to root, privilege dropping did not work"); + } return true; }