1
0
Fork 0
apt/methods/aptmethod.h
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

614 lines
16 KiB
C++

#ifndef APT_APTMETHOD_H
#define APT_APTMETHOD_H
#include "config.h"
#include <apt-pkg/acquire-method.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/netrc.h>
#include <apt-pkg/strutl.h>
#include <algorithm>
#include <locale>
#include <memory>
#include <string>
#include <vector>
#include <cstdlib>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <apti18n.h>
#ifdef HAVE_SECCOMP
#include <csignal>
#include <seccomp.h>
#endif
enum class ResultState
{
TRANSIENT_ERROR,
FATAL_ERROR,
SUCCESSFUL
};
static bool hasDoubleColon(std::string const &n)
{
return n.find("::") != std::string::npos;
}
class aptConfigWrapperForMethods
{
protected:
std::vector<std::string> methodNames;
public:
void setPostfixForMethodNames(char const * const postfix) APT_NONNULL(2)
{
methodNames.erase(std::remove_if(methodNames.begin(), methodNames.end(), hasDoubleColon), methodNames.end());
decltype(methodNames) toAdd;
for (auto && name: methodNames)
toAdd.emplace_back(name + "::" + postfix);
std::move(toAdd.begin(), toAdd.end(), std::back_inserter(methodNames));
}
bool DebugEnabled() const
{
if (methodNames.empty())
return false;
auto const sni = std::find_if_not(methodNames.crbegin(), methodNames.crend(), hasDoubleColon);
if (unlikely(sni == methodNames.crend()))
return false;
auto const ln = methodNames[methodNames.size() - 1];
// worst case: all three are the same
std::string confln, confsn, confpn;
strprintf(confln, "Debug::Acquire::%s", ln.c_str());
strprintf(confsn, "Debug::Acquire::%s", sni->c_str());
auto const pni = sni->substr(0, sni->find('+'));
strprintf(confpn, "Debug::Acquire::%s", pni.c_str());
return _config->FindB(confln,_config->FindB(confsn, _config->FindB(confpn, false)));
}
std::string ConfigFind(char const * const postfix, std::string const &defValue) const APT_NONNULL(2)
{
for (auto name = methodNames.rbegin(); name != methodNames.rend(); ++name)
{
std::string conf;
strprintf(conf, "Acquire::%s::%s", name->c_str(), postfix);
auto value = _config->Find(conf);
if (not value.empty())
return value;
}
return defValue;
}
std::string ConfigFind(std::string const &postfix, std::string const &defValue) const
{
return ConfigFind(postfix.c_str(), defValue);
}
bool ConfigFindB(char const * const postfix, bool const defValue) const APT_NONNULL(2)
{
return StringToBool(ConfigFind(postfix, defValue ? "yes" : "no"), defValue);
}
int ConfigFindI(char const * const postfix, int const defValue) const APT_NONNULL(2)
{
char *End;
std::string const value = ConfigFind(postfix, "");
auto const Res = strtol(value.c_str(), &End, 0);
if (value.c_str() == End)
return defValue;
return Res;
}
explicit aptConfigWrapperForMethods(std::string const &name) : methodNames{{name}} {}
explicit aptConfigWrapperForMethods(std::vector<std::string> &&names) : methodNames{std::move(names)} {}
};
class aptMethod : public pkgAcqMethod, public aptConfigWrapperForMethods
{
protected:
std::string const Binary;
unsigned long SeccompFlags;
enum Seccomp
{
BASE = (1 << 1),
NETWORK = (1 << 2),
DIRECTORY = (1 << 3),
};
public:
bool Configuration(std::string Message) override
{
if (pkgAcqMethod::Configuration(Message) == false)
return false;
std::string const conf = std::string("Binary::") + Binary;
_config->MoveSubTree(conf.c_str(), NULL);
DropPrivsOrDie();
if (LoadSeccomp() == false)
return false;
return true;
}
bool RunningInQemu(void)
{
int status;
pid_t pid;
pid = fork();
if (pid == 0)
{
close(0);
close(1);
close(2);
setenv("QEMU_VERSION", "meow", 1);
char path[] = LIBEXEC_DIR "/apt-helper";
char *const argv[] = {path, NULL};
execv(argv[0], argv);
_exit(255);
}
// apt-helper is supposed to exit with an error. If it exited with 0,
// qemu-user had problems with QEMU_VERSION and returned 0 => running in
// qemu-user.
if (waitpid(pid, &status, 0) == pid && WIFEXITED(status) && WEXITSTATUS(status) == 0)
return true;
return false;
}
bool LoadSeccomp()
{
#ifdef HAVE_SECCOMP
int rc;
scmp_filter_ctx ctx = NULL;
if (SeccompFlags == 0)
return true;
if (_config->FindB("APT::Sandbox::Seccomp", false) == false)
return true;
if (RunningInQemu() == true)
{
Warning("Running in qemu-user, not using seccomp");
return true;
}
ctx = seccomp_init(SCMP_ACT_TRAP);
if (ctx == NULL)
return _error->FatalE("HttpMethod::Configuration", "Cannot init seccomp");
#define ALLOW(what) \
if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(what), 0))) \
return _error->FatalE("HttpMethod::Configuration", "Cannot allow %s: %s", #what, strerror(-rc));
for (auto &custom : _config->FindVector("APT::Sandbox::Seccomp::Trap"))
{
if ((rc = seccomp_rule_add(ctx, SCMP_ACT_TRAP, seccomp_syscall_resolve_name(custom.c_str()), 0)))
return _error->FatalE("HttpMethod::Configuration", "Cannot trap %s: %s", custom.c_str(), strerror(-rc));
}
ALLOW(access);
ALLOW(arch_prctl);
ALLOW(brk);
ALLOW(chmod);
ALLOW(chown);
ALLOW(chown32);
ALLOW(clock_getres);
ALLOW(clock_getres_time64);
ALLOW(clock_gettime);
ALLOW(clock_gettime64);
ALLOW(clock_nanosleep);
ALLOW(clock_nanosleep_time64);
ALLOW(close);
ALLOW(creat);
ALLOW(dup);
ALLOW(dup2);
ALLOW(dup3);
ALLOW(exit);
ALLOW(exit_group);
ALLOW(faccessat);
ALLOW(fchmod);
ALLOW(fchmodat);
ALLOW(fchown);
ALLOW(fchown32);
ALLOW(fchownat);
ALLOW(fcntl);
ALLOW(fcntl64);
ALLOW(fdatasync);
ALLOW(flock);
ALLOW(fstat);
ALLOW(fstat64);
ALLOW(fstatat64);
ALLOW(fstatfs);
ALLOW(fstatfs64);
ALLOW(fsync);
ALLOW(ftime);
ALLOW(ftruncate);
ALLOW(ftruncate64);
ALLOW(futex);
ALLOW(futex_time64);
ALLOW(futimesat);
ALLOW(getegid);
ALLOW(getegid32);
ALLOW(geteuid);
ALLOW(geteuid32);
ALLOW(getgid);
ALLOW(getgid32);
ALLOW(getgroups);
ALLOW(getgroups32);
ALLOW(getpeername);
ALLOW(getpgid);
ALLOW(getpgrp);
ALLOW(getpid);
ALLOW(getppid);
ALLOW(getrandom);
ALLOW(getresgid);
ALLOW(getresgid32);
ALLOW(getresuid);
ALLOW(getresuid32);
ALLOW(getrlimit);
ALLOW(get_robust_list);
ALLOW(getrusage);
ALLOW(gettid);
ALLOW(gettimeofday);
ALLOW(getuid);
ALLOW(getuid32);
ALLOW(ioctl);
ALLOW(lchown);
ALLOW(lchown32);
ALLOW(_llseek);
ALLOW(lseek);
ALLOW(lstat);
ALLOW(lstat64);
ALLOW(madvise);
ALLOW(mmap);
ALLOW(mmap2);
ALLOW(mprotect);
ALLOW(mremap);
ALLOW(msync);
ALLOW(munmap);
ALLOW(nanosleep);
ALLOW(newfstatat);
ALLOW(_newselect);
ALLOW(oldfstat);
ALLOW(oldlstat);
ALLOW(oldolduname);
ALLOW(oldstat);
ALLOW(olduname);
ALLOW(open);
ALLOW(openat);
ALLOW(pipe);
ALLOW(pipe2);
ALLOW(poll);
ALLOW(ppoll);
ALLOW(ppoll_time64);
ALLOW(prctl);
ALLOW(prlimit64);
ALLOW(pselect6);
ALLOW(pselect6_time64);
ALLOW(read);
ALLOW(readv);
ALLOW(rename);
ALLOW(renameat);
ALLOW(renameat2);
ALLOW(restart_syscall);
ALLOW(rt_sigaction);
ALLOW(rt_sigpending);
ALLOW(rt_sigprocmask);
ALLOW(rt_sigqueueinfo);
ALLOW(rt_sigreturn);
ALLOW(rt_sigsuspend);
ALLOW(rt_sigtimedwait);
ALLOW(sched_yield);
ALLOW(select);
ALLOW(set_robust_list);
ALLOW(sigaction);
ALLOW(sigpending);
ALLOW(sigprocmask);
ALLOW(sigreturn);
ALLOW(sigsuspend);
ALLOW(stat);
ALLOW(stat64);
ALLOW(statfs);
ALLOW(statfs64);
#ifdef __NR_statx
ALLOW(statx);
#endif
ALLOW(sync);
ALLOW(syscall);
ALLOW(sysinfo);
ALLOW(time);
ALLOW(truncate);
ALLOW(truncate64);
ALLOW(ugetrlimit);
ALLOW(umask);
ALLOW(uname);
ALLOW(unlink);
ALLOW(unlinkat);
ALLOW(utime);
ALLOW(utimensat);
ALLOW(utimensat_time64);
ALLOW(utimes);
ALLOW(write);
ALLOW(writev);
if ((SeccompFlags & Seccomp::NETWORK) != 0)
{
ALLOW(bind);
ALLOW(connect);
ALLOW(getsockname);
ALLOW(getsockopt);
ALLOW(recv);
ALLOW(recvfrom);
ALLOW(recvmmsg);
ALLOW(recvmmsg_time64);
ALLOW(recvmsg);
ALLOW(send);
ALLOW(sendmmsg);
ALLOW(sendmsg);
ALLOW(sendto);
ALLOW(setsockopt);
ALLOW(shutdown);
ALLOW(socket);
ALLOW(socketcall);
}
if ((SeccompFlags & Seccomp::DIRECTORY) != 0)
{
ALLOW(readdir);
ALLOW(getdents);
ALLOW(getdents64);
}
if (getenv("FAKED_MODE"))
{
ALLOW(semop);
ALLOW(semget);
ALLOW(msgsnd);
ALLOW(msgrcv);
ALLOW(msgget);
ALLOW(msgctl);
ALLOW(ipc);
}
for (auto &custom : _config->FindVector("APT::Sandbox::Seccomp::Allow"))
{
if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, seccomp_syscall_resolve_name(custom.c_str()), 0)))
return _error->FatalE("aptMethod::Configuration", "Cannot allow %s: %s", custom.c_str(), strerror(-rc));
}
#undef ALLOW
rc = seccomp_load(ctx);
if (rc == -EINVAL)
{
std::string msg;
strprintf(msg, "aptMethod::Configuration: could not load seccomp policy: %s", strerror(-rc));
Warning(std::move(msg));
}
else if (rc != 0)
return _error->FatalE("aptMethod::Configuration", "could not load seccomp policy: %s", strerror(-rc));
if (_config->FindB("APT::Sandbox::Seccomp::Print", true))
{
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_sigaction = [](int, siginfo_t *info, void *) {
// Formats a number into a 10 digit ASCII string
char buffer[10];
int number = info->si_syscall;
for (int i = sizeof(buffer) - 1; i >= 0; i--)
{
buffer[i] = (number % 10) + '0';
number /= 10;
}
constexpr const char *str1 = "\n **** Seccomp prevented execution of syscall ";
constexpr const char *str2 = " on architecture ";
constexpr const char *str3 = " ****\n";
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
write(2, str1, strlen(str1));
write(2, buffer, sizeof(buffer));
write(2, str2, strlen(str2));
write(2, COMMON_ARCH, strlen(COMMON_ARCH));
write(2, str3, strlen(str3));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
_exit(31);
};
action.sa_flags = SA_SIGINFO;
sigaction(SIGSYS, &action, nullptr);
}
#endif
return true;
}
bool CalculateHashes(FetchItem const * const Itm, FetchResult &Res) const APT_NONNULL(2)
{
Hashes Hash(Itm->ExpectedHashes);
FileFd Fd;
if (Fd.Open(Res.Filename, FileFd::ReadOnly) == false || Hash.AddFD(Fd) == false)
return false;
Res.TakeHashes(Hash);
return true;
}
void Message(std::string &&msg, std::string code)
{
std::unordered_map<std::string, std::string> fields;
if (Queue != 0)
fields.emplace("URI", Queue->Uri);
else
fields.emplace("URI", "<UNKNOWN>");
if (not UsedMirror.empty())
fields.emplace("UsedMirror", UsedMirror);
fields.emplace("Message", std::move(msg));
SendMessage(code, std::move(fields));
}
void Warning(std::string &&msg)
{
return Message(std::move(msg), "104 Warning");
}
void Audit(std::string &&msg)
{
return Message(std::move(msg), "105 Audit");
}
bool TransferModificationTimes(char const * const From, char const * const To, time_t &LastModified) APT_NONNULL(2, 3)
{
if (strcmp(To, "/dev/null") == 0)
return true;
struct stat Buf2;
if (lstat(To, &Buf2) != 0 || S_ISLNK(Buf2.st_mode))
return true;
struct stat Buf;
if (stat(From, &Buf) != 0)
return _error->Errno("stat",_("Failed to stat"));
// we don't use utimensat here for compatibility reasons: #738567
struct timeval times[2];
times[0].tv_sec = Buf.st_atime;
LastModified = times[1].tv_sec = Buf.st_mtime;
times[0].tv_usec = times[1].tv_usec = 0;
if (utimes(To, times) != 0)
return _error->Errno("utimes",_("Failed to set modification time"));
return true;
}
// This is a copy of #pkgAcqMethod::Dequeue which is private & hidden
void Dequeue()
{
FetchItem const *const Tmp = Queue;
Queue = Queue->Next;
if (Tmp == QueueBack)
QueueBack = Queue;
delete Tmp;
}
static std::string URIEncode(std::string const &part)
{
// The "+" is encoded as a workaround for an S3 bug (LP#1003633 and LP#1086997)
return QuoteString(part, _config->Find("Acquire::URIEncode", "+~ ").c_str());
}
static std::string DecodeSendURI(std::string const &part)
{
if (_config->FindB("Acquire::Send-URI-Encoded", false))
return DeQuoteString(part);
return part;
}
static std::string CombineWithAlternatePath(std::string Path, std::string Change)
{
while (APT::String::Startswith(Change, "../"))
{
Path.erase(Path.find_last_not_of('/'));
Path = flNotFile(Path);
Change.erase(0, 3);
}
return flCombine(Path, Change);
}
aptMethod(std::string &&Binary, char const *const Ver, unsigned long const Flags) APT_NONNULL(3)
: pkgAcqMethod(Ver, Flags), aptConfigWrapperForMethods(Binary), Binary(std::move(Binary)), SeccompFlags(0)
{
try {
std::locale::global(std::locale(""));
} catch (...) {
setlocale(LC_ALL, "");
}
}
};
class aptAuthConfMethod : public aptMethod
{
std::vector<std::unique_ptr<FileFd>> authconfs;
public:
bool Configuration(std::string Message) override
{
if (pkgAcqMethod::Configuration(Message) == false)
return false;
std::string const conf = std::string("Binary::") + Binary;
_config->MoveSubTree(conf.c_str(), NULL);
// ignore errors with opening the auth file as it doesn't need to exist
_error->PushToStack();
auto const netrc = _config->FindFile("Dir::Etc::netrc");
if (netrc.empty() == false)
{
authconfs.emplace_back(new FileFd());
authconfs.back()->Open(netrc, FileFd::ReadOnly);
}
auto const netrcparts = _config->FindDir("Dir::Etc::netrcparts");
if (netrcparts.empty() == false)
{
for (auto &&netrcpart : GetListOfFilesInDir(netrcparts, "conf", true, true))
{
authconfs.emplace_back(new FileFd());
authconfs.back()->Open(netrcpart, FileFd::ReadOnly);
}
}
_error->RevertToStack();
DropPrivsOrDie();
if (LoadSeccomp() == false)
return false;
return true;
}
bool MaybeAddAuthTo(URI &uri)
{
bool result = true;
if (uri.User.empty() == false || uri.Password.empty() == false)
return true;
_error->PushToStack();
for (auto &authconf : authconfs)
{
if (authconf->IsOpen() == false)
continue;
if (authconf->Seek(0) == false)
{
result = false;
continue;
}
result &= MaybeAddAuth(*authconf, uri);
}
while (not _error->empty())
{
std::string message;
_error->PopMessage(message);
Warning(std::move(message));
}
_error->RevertToStack();
return result;
}
aptAuthConfMethod(std::string &&Binary, char const *const Ver, unsigned long const Flags) APT_NONNULL(3)
: aptMethod(std::move(Binary), Ver, Flags) {}
};
#endif