From 7c8206bf26b8ef6020b543bbc027305dee8f2308 Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Mon, 31 Aug 2015 11:00:12 +0200 Subject: if file is inaccessible for _apt, disable privilege drop in acquire We had a very similar method previously for our own private usage, but with some generalisation we can move this check into the acquire system proper so that all frontends profit from this compatibility change. As we are disabling a security feature here a warning is issued and frontends are advised to consider reworking their download logic if possible. Note that this is implemented as an all or nothing situation: We can't just (not) drop privileges for a subset of the files in a fetcher, so in case you have to download some files with and some without you need to use two fetchers. --- apt-pkg/acquire.cc | 51 ++++++++++++++++++++++ apt-pkg/acquire.h | 2 + apt-private/private-download.cc | 49 --------------------- apt-private/private-download.h | 2 - cmdline/apt-get.cc | 8 ---- cmdline/apt-helper.cc | 3 -- .../test-acquire-same-file-multiple-times | 3 +- test/integration/test-apt-get-changelog | 8 +++- 8 files changed, 60 insertions(+), 66 deletions(-) diff --git a/apt-pkg/acquire.cc b/apt-pkg/acquire.cc index f8b077367..cb32e8f2b 100644 --- a/apt-pkg/acquire.cc +++ b/apt-pkg/acquire.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -446,8 +447,58 @@ void pkgAcquire::RunFds(fd_set *RSet,fd_set *WSet) /* This runs the queues. It manages a select loop for all of the Worker tasks. The workers interact with the queues and items to manage the actual fetch. */ +static void CheckDropPrivsMustBeDisabled(pkgAcquire const &Fetcher) +{ + if(getuid() != 0) + return; + + std::string SandboxUser = _config->Find("APT::Sandbox::User"); + if (SandboxUser.empty()) + return; + + struct passwd const * const pw = getpwnam(SandboxUser.c_str()); + if (pw == NULL) + return; + + 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); + + bool dropPrivs = true; + for (pkgAcquire::ItemCIterator I = Fetcher.ItemsBegin(); + I != Fetcher.ItemsEnd() && dropPrivs == true; ++I) + { + if ((*I)->DestFile.empty()) + continue; + + // we check directory instead of file as the file might or might not + // exist already as a link or not which complicates everything… + std::string dirname = flNotFile((*I)->DestFile); + if (DirectoryExists(dirname)) + ; + else + continue; // assume it is created correctly by the acquire system + + if (faccessat(AT_FDCWD, dirname.c_str(), R_OK | W_OK | X_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW) != 0) + { + dropPrivs = false; + _error->WarningE("pkgAcquire::Run", _("Can't drop privileges for downloading as file '%s' couldn't be accessed by user '%s'."), + (*I)->DestFile.c_str(), SandboxUser.c_str()); + _config->Set("APT::Sandbox::User", ""); + break; + } + } + + if (seteuid(0) != 0) + _error->Errno("seteuid", "seteuid %u failed", 0); + if (setegid(0) != 0) + _error->Errno("setegid", "setegid %u failed", 0); +} pkgAcquire::RunResult pkgAcquire::Run(int PulseIntervall) { + CheckDropPrivsMustBeDisabled(*this); + Running = true; for (Queue *I = Queues; I != 0; I = I->Next) diff --git a/apt-pkg/acquire.h b/apt-pkg/acquire.h index 10025a6ef..3e5ca41cd 100644 --- a/apt-pkg/acquire.h +++ b/apt-pkg/acquire.h @@ -303,9 +303,11 @@ class pkgAcquire /** \brief Get the head of the list of items. */ inline ItemIterator ItemsBegin() {return Items.begin();}; + inline ItemCIterator ItemsBegin() const {return Items.begin();}; /** \brief Get the end iterator of the list of items. */ inline ItemIterator ItemsEnd() {return Items.end();}; + inline ItemCIterator ItemsEnd() const {return Items.end();}; // Iterate over queued Item URIs class UriIterator; diff --git a/apt-private/private-download.cc b/apt-private/private-download.cc index 18a9b1fbc..8a57ccc86 100644 --- a/apt-private/private-download.cc +++ b/apt-private/private-download.cc @@ -26,55 +26,6 @@ #include /*}}}*/ -bool CheckDropPrivsMustBeDisabled(pkgAcquire &Fetcher) /*{{{*/ -{ - // no need/possibility to drop privs - if(getuid() != 0) - return true; - - // the user does not want to drop privs - std::string SandboxUser = _config->Find("APT::Sandbox::User"); - if (SandboxUser.empty()) - return true; - - struct passwd const * const pw = getpwnam(SandboxUser.c_str()); - if (pw == NULL) - return true; - - if (seteuid(pw->pw_uid) != 0) - return _error->Errno("seteuid", "seteuid %u failed", pw->pw_uid); - - bool res = true; - // check if we can write to destfile - for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); - I != Fetcher.ItemsEnd() && res == true; ++I) - { - if ((*I)->DestFile.empty()) - continue; - // we assume that an existing (partial) file means that we have sufficient rights - if (RealFileExists((*I)->DestFile)) - continue; - int fd = open((*I)->DestFile.c_str(), O_CREAT | O_EXCL | O_RDWR, 0600); - if (fd < 0) - { - res = false; - std::string msg; - strprintf(msg, _("Can't drop privileges for downloading as file '%s' couldn't be accessed by user '%s'."), - (*I)->DestFile.c_str(), SandboxUser.c_str()); - std::cerr << "W: " << msg << std::endl; - _config->Set("APT::Sandbox::User", ""); - break; - } - unlink((*I)->DestFile.c_str()); - close(fd); - } - - if (seteuid(0) != 0) - return _error->Errno("seteuid", "seteuid %u failed", 0); - - return res; -} - /*}}}*/ // CheckAuth - check if each download comes form a trusted source /*{{{*/ bool CheckAuth(pkgAcquire& Fetcher, bool const PromptUser) { diff --git a/apt-private/private-download.h b/apt-private/private-download.h index 0f3db5e7a..80643e0f2 100644 --- a/apt-private/private-download.h +++ b/apt-private/private-download.h @@ -8,8 +8,6 @@ class pkgAcquire; -APT_PUBLIC bool CheckDropPrivsMustBeDisabled(pkgAcquire &Fetcher); - // Check if all files in the fetcher are authenticated APT_PUBLIC bool CheckAuth(pkgAcquire& Fetcher, bool const PromptUser); diff --git a/cmdline/apt-get.cc b/cmdline/apt-get.cc index d3b3da240..ebc8c94c2 100644 --- a/cmdline/apt-get.cc +++ b/cmdline/apt-get.cc @@ -629,9 +629,6 @@ static bool DoDownload(CommandLine &CmdL) return true; } - // Disable drop-privs if "_apt" can not write to the target dir - CheckDropPrivsMustBeDisabled(Fetcher); - if (_error->PendingError() == true || CheckAuth(Fetcher, false) == false) return false; @@ -850,9 +847,6 @@ static bool DoSource(CommandLine &CmdL) return true; } - // Disable drop-privs if "_apt" can not write to the target dir - CheckDropPrivsMustBeDisabled(Fetcher); - // check authentication status of the source as well if (UntrustedList.empty() == false && AuthPrompt(UntrustedList, false) == false) return false; @@ -1403,8 +1397,6 @@ static bool DoChangelog(CommandLine &CmdL) if (printOnly == false) { - // Note: CheckDropPrivsMustBeDisabled isn't needed here as the download happens in a dedicated tempdir - bool Failed = false; if (AcquireRun(Fetcher, 0, &Failed, NULL) == false || Failed == true) return false; diff --git a/cmdline/apt-helper.cc b/cmdline/apt-helper.cc index d235ac315..3c49bf149 100644 --- a/cmdline/apt-helper.cc +++ b/cmdline/apt-helper.cc @@ -68,9 +68,6 @@ static bool DoDownloadFile(CommandLine &CmdL) fileind += 3; } - // Disable drop-privs if "_apt" can not write to the target dir - CheckDropPrivsMustBeDisabled(Fetcher); - bool Failed = false; if (AcquireRun(Fetcher, 0, &Failed, NULL) == false || Failed == true) return _error->Error(_("Download Failed")); diff --git a/test/integration/test-acquire-same-file-multiple-times b/test/integration/test-acquire-same-file-multiple-times index 526765521..a850f897c 100755 --- a/test/integration/test-acquire-same-file-multiple-times +++ b/test/integration/test-acquire-same-file-multiple-times @@ -14,10 +14,9 @@ filedown() { msgtest 'Downloading the same URI twice over file' "$1" testsuccess --nomsg apthelper download-file file:///$APTARCHIVE/foo ./downloaded/foo1 '' file:///$APTARCHIVE/foo ./downloaded/foo2 '' -o Debug::pkgAcquire::Worker=1 cp rootdir/tmp/testsuccess.output download.log - #cat download.log testsuccess cmp $TESTFILE ./downloaded/foo1 testsuccess cmp ./downloaded/foo1 ./downloaded/foo2 - #testequal '1' grep -c '200%20URI%20Start' ./download.log + testequal '1' grep -c '200%20URI%20Start' ./download.log testequal '1' grep -c '201%20URI%20Done' ./download.log rm -f ./downloaded/foo1 ./downloaded/foo2 } diff --git a/test/integration/test-apt-get-changelog b/test/integration/test-apt-get-changelog index 6ca05d0fa..502920617 100755 --- a/test/integration/test-apt-get-changelog +++ b/test/integration/test-apt-get-changelog @@ -47,8 +47,12 @@ testsuccessequal "'http://localhost:8080/main/f/foo/foo_1.0.changelog' foo.chang 'http://localhost:8080/main/libb/libbar/libbar_1.0.changelog' libbar.changelog" aptget changelog foo libbar --print-uris -o Acquire::Changelogs::URI::Override::Label::Debian='http://localhost:8080/CHANGEPATH.changelog' releasechanger 'Changelogs' 'no' -testequal 'E: Failed to fetch changelog:/foo.changelog Changelog unavailable for foo=1.0 -' aptget changelog foo -qq -d +if [ "$(id -u)" = '0' ]; then + testfailuremsg "W: Can't drop privileges for downloading as file 'foo.changelog' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied) +E: Failed to fetch changelog:/foo.changelog Changelog unavailable for foo=1.0" aptget changelog foo -d +else + testfailuremsg 'E: Failed to fetch changelog:/foo.changelog Changelog unavailable for foo=1.0' aptget changelog foo -d +fi sed -i '/^Changelogs: / d' $(find rootdir/var/lib/apt/lists -name '*Release') releasechanger 'Label' 'Testcases' -- cgit v1.2.3