hyprpm: clean up root access and properly check input (#10304)
* manifest: reject bad names from parsing * sys: restructure root functions
This commit is contained in:
parent
948277895e
commit
f8bbe5124c
7 changed files with 224 additions and 111 deletions
|
|
@ -3,11 +3,37 @@
|
|||
#include <toml++/toml.hpp>
|
||||
#include <print>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include "PluginManager.hpp"
|
||||
#include "../helpers/Die.hpp"
|
||||
#include "../helpers/Sys.hpp"
|
||||
#include "../helpers/StringUtils.hpp"
|
||||
|
||||
static std::string getTempRoot() {
|
||||
static auto ENV = getenv("XDG_RUNTIME_DIR");
|
||||
if (!ENV) {
|
||||
std::cerr << "\nERROR: XDG_RUNTIME_DIR not set!\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto STR = ENV + std::string{"/hyprpm/"};
|
||||
|
||||
return STR;
|
||||
}
|
||||
|
||||
// write the state to a file
|
||||
static bool writeState(const std::string& str, const std::string& to) {
|
||||
// create temp file in a safe temp root
|
||||
std::ofstream of(getTempRoot() + ".temp-state", std::ios::trunc);
|
||||
if (!of.good())
|
||||
return false;
|
||||
|
||||
of << str;
|
||||
of.close();
|
||||
|
||||
return NSys::root::install(getTempRoot() + ".temp-state", to, "644");
|
||||
}
|
||||
|
||||
std::filesystem::path DataState::getDataStatePath() {
|
||||
return std::filesystem::path("/var/cache/hyprpm/" + g_pPluginManager->m_szUsername);
|
||||
}
|
||||
|
|
@ -37,11 +63,16 @@ void DataState::ensureStateStoreExists() {
|
|||
std::error_code ec;
|
||||
if (!std::filesystem::exists(getHeadersPath(), ec) || ec) {
|
||||
std::println("{}", infoString("The hyprpm state store doesn't exist. Creating now..."));
|
||||
if (!std::filesystem::exists("/var/cache/hyprpm/", ec) || ec)
|
||||
NSys::runAsSuperuser("mkdir -p -m 755 '/var/cache/hyprpm/'");
|
||||
if (!std::filesystem::exists(getDataStatePath(), ec) || ec)
|
||||
NSys::runAsSuperuser("mkdir -p -m 755 '" + getDataStatePath().string() + "'");
|
||||
NSys::runAsSuperuser("mkdir -p -m 755 '" + getHeadersPath() + "'");
|
||||
if (!std::filesystem::exists("/var/cache/hyprpm/", ec) || ec) {
|
||||
if (!NSys::root::createDirectory("/var/cache/hyprpm", "755"))
|
||||
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
|
||||
}
|
||||
if (!std::filesystem::exists(getDataStatePath(), ec) || ec) {
|
||||
if (!NSys::root::createDirectory(getDataStatePath().string(), "755"))
|
||||
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
|
||||
}
|
||||
if (!NSys::root::createDirectory(getHeadersPath(), "755"))
|
||||
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,8 +82,10 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
|||
const auto PATH = getDataStatePath() / repo.name;
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(PATH, ec) || ec)
|
||||
NSys::runAsSuperuser("mkdir -p -m 755 '" + PATH.string() + "'");
|
||||
if (!std::filesystem::exists(PATH, ec) || ec) {
|
||||
if (!NSys::root::createDirectory(PATH.string(), "755"))
|
||||
Debug::die("addNewPluginRepo: failed to create cache dir");
|
||||
}
|
||||
// clang-format off
|
||||
auto DATA = toml::table{
|
||||
{"repository", toml::table{
|
||||
|
|
@ -66,8 +99,10 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
|||
const auto filename = p.name + ".so";
|
||||
|
||||
// copy .so to the good place and chmod 755
|
||||
if (std::filesystem::exists(p.filename))
|
||||
NSys::runAsSuperuser("cp '" + p.filename + "' '" + (PATH / filename).string() + "' && chmod 755 '" + (PATH / filename).string() + "'");
|
||||
if (std::filesystem::exists(p.filename)) {
|
||||
if (!NSys::root::install(p.filename, (PATH / filename).string(), "0755"))
|
||||
Debug::die("addNewPluginRepo: failed to install so file");
|
||||
}
|
||||
|
||||
DATA.emplace(p.name, toml::table{
|
||||
{"filename", filename},
|
||||
|
|
@ -80,8 +115,8 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
|||
std::stringstream ss;
|
||||
ss << DATA;
|
||||
|
||||
NSys::runAsSuperuser("cat << EOF > " + (PATH / "state.toml").string() + "\n" + ss.str() + "\nEOF");
|
||||
NSys::runAsSuperuser("chmod 644 '" + (PATH / "state.toml").string() + "'");
|
||||
if (!writeState(ss.str(), (PATH / "state.toml").string()))
|
||||
Debug::die("{}", failureString("Failed to write plugin state"));
|
||||
}
|
||||
|
||||
bool DataState::pluginRepoExists(const std::string& urlOrName) {
|
||||
|
|
@ -123,7 +158,8 @@ void DataState::removePluginRepo(const std::string& urlOrName) {
|
|||
return; // WTF?
|
||||
|
||||
// scary!
|
||||
NSys::runAsSuperuser("rm -r '" + PATH + "'");
|
||||
if (!NSys::root::removeRecursive(PATH))
|
||||
Debug::die("removePluginRepo: failed to remove dir");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,8 +171,10 @@ void DataState::updateGlobalState(const SGlobalState& state) {
|
|||
const auto PATH = getDataStatePath();
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(PATH, ec) || ec)
|
||||
NSys::runAsSuperuser("mkdir -p -m 755 '" + PATH.string() + "'");
|
||||
if (!std::filesystem::exists(PATH, ec) || ec) {
|
||||
if (!NSys::root::createDirectory(PATH.string(), "755"))
|
||||
Debug::die("updateGlobalState: failed to create dir");
|
||||
}
|
||||
// clang-format off
|
||||
auto DATA = toml::table{
|
||||
{"state", toml::table{
|
||||
|
|
@ -149,8 +187,8 @@ void DataState::updateGlobalState(const SGlobalState& state) {
|
|||
std::stringstream ss;
|
||||
ss << DATA;
|
||||
|
||||
NSys::runAsSuperuser("cat << EOF > " + (PATH / "state.toml").string() + "\n" + ss.str() + "\nEOF");
|
||||
NSys::runAsSuperuser("chmod 644 '" + (PATH / "state.toml").string() + "'");
|
||||
if (!writeState(ss.str(), (PATH / "state.toml").string()))
|
||||
Debug::die("{}", failureString("Failed to write plugin state"));
|
||||
}
|
||||
|
||||
SGlobalState DataState::getGlobalState() {
|
||||
|
|
@ -229,8 +267,8 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
|
|||
std::stringstream ss;
|
||||
ss << modifiedState;
|
||||
|
||||
NSys::runAsSuperuser("cat << EOF > " + stateFile.string() + "\n" + ss.str() + "\nEOF");
|
||||
NSys::runAsSuperuser("chmod 644 '" + stateFile.string() + "'");
|
||||
if (!writeState(ss.str(), stateFile.string()))
|
||||
Debug::die("{}", failureString("Failed to write plugin state"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -250,5 +288,6 @@ void DataState::purgeAllCache() {
|
|||
if (PATH.contains('\''))
|
||||
return;
|
||||
// scary!
|
||||
NSys::runAsSuperuser("rm -r '" + PATH + "'");
|
||||
if (!NSys::root::removeRecursive(PATH))
|
||||
Debug::die("Failed to run a superuser cmd");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
#include "Manifest.hpp"
|
||||
#include <toml++/toml.hpp>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
// Alphanumerics and -_ allowed for plugin names. No magic names.
|
||||
// [A-Za-z0-9\-_]*
|
||||
static bool validManifestName(const std::string_view& n) {
|
||||
return std::ranges::all_of(n, [](const char& c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || c == '=' || (c >= '0' && c <= '9'); });
|
||||
}
|
||||
|
||||
CManifest::CManifest(const eManifestType type, const std::string& path) {
|
||||
auto manifest = toml::parse_file(path);
|
||||
|
|
@ -11,11 +17,17 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
|
|||
continue;
|
||||
|
||||
CManifest::SManifestPlugin plugin;
|
||||
|
||||
if (!validManifestName(key.str())) {
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.name = key;
|
||||
m_vPlugins.push_back(plugin);
|
||||
m_plugins.push_back(plugin);
|
||||
}
|
||||
|
||||
for (auto& plugin : m_vPlugins) {
|
||||
for (auto& plugin : m_plugins) {
|
||||
plugin.description = manifest[plugin.name]["description"].value_or("?");
|
||||
plugin.version = manifest[plugin.name]["version"].value_or("?");
|
||||
plugin.output = manifest[plugin.name]["build"]["output"].value_or("?");
|
||||
|
|
@ -37,21 +49,21 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
|
|||
}
|
||||
|
||||
if (plugin.output.empty() || plugin.buildSteps.empty()) {
|
||||
m_bGood = false;
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (type == MANIFEST_HYPRPM) {
|
||||
m_sRepository.name = manifest["repository"]["name"].value_or("");
|
||||
auto authors = manifest["repository"]["authors"].as_array();
|
||||
m_repository.name = manifest["repository"]["name"].value_or("");
|
||||
auto authors = manifest["repository"]["authors"].as_array();
|
||||
if (authors) {
|
||||
for (auto&& a : *authors) {
|
||||
m_sRepository.authors.push_back(a.as_string()->value_or("?"));
|
||||
m_repository.authors.push_back(a.as_string()->value_or("?"));
|
||||
}
|
||||
} else {
|
||||
auto author = manifest["repository"]["author"].value_or("");
|
||||
if (!std::string{author}.empty())
|
||||
m_sRepository.authors.push_back(author);
|
||||
m_repository.authors.push_back(author);
|
||||
}
|
||||
|
||||
auto pins = manifest["repository"]["commit_pins"].as_array();
|
||||
|
|
@ -59,7 +71,7 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
|
|||
for (auto&& pin : *pins) {
|
||||
auto pinArr = pin.as_array();
|
||||
if (pinArr && pinArr->get(1))
|
||||
m_sRepository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
|
||||
m_repository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,11 +80,17 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
|
|||
continue;
|
||||
|
||||
CManifest::SManifestPlugin plugin;
|
||||
|
||||
if (!validManifestName(key.str())) {
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.name = key;
|
||||
m_vPlugins.push_back(plugin);
|
||||
m_plugins.push_back(plugin);
|
||||
}
|
||||
|
||||
for (auto& plugin : m_vPlugins) {
|
||||
for (auto& plugin : m_plugins) {
|
||||
plugin.description = manifest[plugin.name]["description"].value_or("?");
|
||||
plugin.output = manifest[plugin.name]["output"].value_or("?");
|
||||
plugin.since = manifest[plugin.name]["since_hyprland"].value_or(0);
|
||||
|
|
@ -94,12 +112,12 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
|
|||
}
|
||||
|
||||
if (plugin.output.empty() || plugin.buildSteps.empty()) {
|
||||
m_bGood = false;
|
||||
m_good = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ???
|
||||
m_bGood = false;
|
||||
m_good = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,8 +27,8 @@ class CManifest {
|
|||
std::string name;
|
||||
std::vector<std::string> authors;
|
||||
std::vector<std::pair<std::string, std::string>> commitPins;
|
||||
} m_sRepository;
|
||||
} m_repository;
|
||||
|
||||
std::vector<SManifestPlugin> m_vPlugins;
|
||||
bool m_bGood = true;
|
||||
std::vector<SManifestPlugin> m_plugins;
|
||||
bool m_good = true;
|
||||
};
|
||||
|
|
@ -236,14 +236,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!pManifest->m_bGood) {
|
||||
if (!pManifest->m_good) {
|
||||
std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.m_iSteps = 2;
|
||||
progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:"));
|
||||
for (auto const& pl : pManifest->m_vPlugins) {
|
||||
progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_plugins.size()) + " plugins:"));
|
||||
for (auto const& pl : pManifest->m_plugins) {
|
||||
std::string message = "→ " + pl.name + " by ";
|
||||
for (auto const& a : pl.authors) {
|
||||
message += a + ", ";
|
||||
|
|
@ -256,12 +256,12 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
progress.printMessageAbove(message);
|
||||
}
|
||||
|
||||
if (!pManifest->m_sRepository.commitPins.empty()) {
|
||||
if (!pManifest->m_repository.commitPins.empty()) {
|
||||
// check commit pins
|
||||
|
||||
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
|
||||
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size()));
|
||||
|
||||
for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
|
||||
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {
|
||||
if (hl != HLVER.hash)
|
||||
continue;
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
progress.m_szCurrentMessage = "Building plugin(s)";
|
||||
progress.print();
|
||||
|
||||
for (auto& p : pManifest->m_vPlugins) {
|
||||
for (auto& p : pManifest->m_plugins) {
|
||||
std::string out;
|
||||
|
||||
if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) {
|
||||
|
|
@ -336,11 +336,11 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
|
||||
if (repohash.length() > 0)
|
||||
repohash.pop_back();
|
||||
repo.name = pManifest->m_sRepository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_sRepository.name;
|
||||
repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name;
|
||||
repo.url = url;
|
||||
repo.rev = rev;
|
||||
repo.hash = repohash;
|
||||
for (auto const& p : pManifest->m_vPlugins) {
|
||||
for (auto const& p : pManifest->m_plugins) {
|
||||
repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed});
|
||||
}
|
||||
DataState::addNewPluginRepo(repo);
|
||||
|
|
@ -566,13 +566,14 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
|
||||
ret = execAndGet(cmd);
|
||||
|
||||
cmd = std::format("cd {} && make installheaders && chmod -R 644 {} && find {} -type d -exec chmod o+x {{}} \\;", WORKINGDIR, DataState::getHeadersPath(),
|
||||
cmd = std::format("make -C '{}' installheaders && chmod -R 644 '{}' && find '{}' -type d -exec chmod o+x {{}} \\;", WORKINGDIR, DataState::getHeadersPath(),
|
||||
DataState::getHeadersPath());
|
||||
|
||||
if (m_bVerbose)
|
||||
progress.printMessageAbove(verboseString("install will run as sudo: {}", cmd));
|
||||
|
||||
ret = NSys::runAsSuperuser(cmd);
|
||||
// WORKINGDIR and headersPath should not contain anything unsafe. Usernames can't contain cmd exec parts.
|
||||
ret = NSys::root::runAsSuperuserUnsafe(cmd);
|
||||
|
||||
if (m_bVerbose)
|
||||
std::println("{}", verboseString("installer returned: {}", ret));
|
||||
|
|
@ -701,17 +702,17 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!pManifest->m_bGood) {
|
||||
std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
|
||||
if (!pManifest->m_good) {
|
||||
std::println(stderr, "\n{}", failureString("The provided plugin repository has a bad manifest"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repo.rev.empty() && !pManifest->m_sRepository.commitPins.empty()) {
|
||||
if (repo.rev.empty() && !pManifest->m_repository.commitPins.empty()) {
|
||||
// check commit pins unless a revision is specified
|
||||
|
||||
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
|
||||
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size()));
|
||||
|
||||
for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
|
||||
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {
|
||||
if (hl != HLVER.hash)
|
||||
continue;
|
||||
|
||||
|
|
@ -721,7 +722,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
}
|
||||
}
|
||||
|
||||
for (auto& p : pManifest->m_vPlugins) {
|
||||
for (auto& p : pManifest->m_plugins) {
|
||||
std::string out;
|
||||
|
||||
if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) {
|
||||
|
|
@ -763,7 +764,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
if (repohash.length() > 0)
|
||||
repohash.pop_back();
|
||||
newrepo.hash = repohash;
|
||||
for (auto const& p : pManifest->m_vPlugins) {
|
||||
for (auto const& p : pManifest->m_plugins) {
|
||||
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
|
||||
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue