core: move to CProcess from hyprutils

this enhances security, avoids calling the shell when invoking the picker
This commit is contained in:
Vaxry 2024-11-15 20:43:31 +00:00
parent 09b23cef06
commit 8070f36dee
5 changed files with 52 additions and 36 deletions

View file

@ -64,7 +64,7 @@ pkg_check_modules(
libdrm libdrm
gbm gbm
hyprlang>=0.2.0 hyprlang>=0.2.0
hyprutils hyprutils>=0.2.6
hyprwayland-scanner>=0.4.2) hyprwayland-scanner>=0.4.2)
# check whether we can find sdbus-c++ through pkg-config # check whether we can find sdbus-c++ through pkg-config

View file

@ -17,6 +17,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets) find_package(Qt6 REQUIRED COMPONENTS Widgets)
find_package(PkgConfig REQUIRED)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
hyprutils>=0.2.6)
set(PROJECT_SOURCES main.cpp mainpicker.cpp mainpicker.h mainpicker.ui set(PROJECT_SOURCES main.cpp mainpicker.cpp mainpicker.h mainpicker.ui
elidedbutton.h elidedbutton.cpp) elidedbutton.h elidedbutton.cpp)
@ -38,7 +45,7 @@ else()
endif() endif()
target_link_libraries(hyprland-share-picker target_link_libraries(hyprland-share-picker
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) PRIVATE Qt${QT_VERSION_MAJOR}::Widgets PkgConfig::deps)
set_target_properties( set_target_properties(
hyprland-share-picker hyprland-share-picker

View file

@ -15,21 +15,20 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::OS;
#include "mainpicker.h" #include "mainpicker.h"
#include "elidedbutton.h" #include "elidedbutton.h"
std::string execAndGet(const char* cmd) { std::string execAndGet(const char* cmd) {
std::array<char, 128> buffer; std::string command = cmd + std::string{" 2>&1"};
std::string result; CProcess proc("/bin/sh", {"-c", cmd});
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) { if (!proc.runSync())
throw std::runtime_error("popen() failed!"); return "error";
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { return proc.stdOut();
result += buffer.data();
}
return result;
} }
QApplication* pickerPtr = nullptr; QApplication* pickerPtr = nullptr;

View file

@ -8,20 +8,17 @@
#include <string> #include <string>
#include <algorithm> #include <algorithm>
std::string execAndGet(const char* cmd) { #include <hyprutils/os/Process.hpp>
Debug::log(LOG, "execAndGet: {}", cmd); using namespace Hyprutils::OS;
std::array<char, 128> buffer; std::string execAndGet(const char* cmd) {
std::string result; std::string command = cmd + std::string{" 2>&1"};
const std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose); CProcess proc("/bin/sh", {"-c", cmd});
if (!pipe) {
Debug::log(ERR, "execAndGet: failed in pipe"); if (!proc.runSync())
return ""; return "error";
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { return proc.stdOut();
result += buffer.data();
}
return result;
} }
void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message) { void addHyprlandNotification(const std::string& icon, float timeMs, const std::string& color, const std::string& message) {

View file

@ -9,10 +9,15 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::OS;
std::string sanitizeNameForWindowList(const std::string& name) { std::string sanitizeNameForWindowList(const std::string& name) {
std::string result = name; std::string result = name;
std::replace(result.begin(), result.end(), '\'', ' '); std::replace(result.begin(), result.end(), '\'', ' ');
std::replace(result.begin(), result.end(), '\"', ' '); std::replace(result.begin(), result.end(), '\"', ' ');
std::replace(result.begin(), result.end(), '$', ' ');
std::replace(result.begin(), result.end(), '`', ' ');
for (size_t i = 1; i < result.size(); ++i) { for (size_t i = 1; i < result.size(); ++i) {
if (result[i - 1] == '>' && result[i] == ']') if (result[i - 1] == '>' && result[i] == ']')
result[i] = ' '; result[i] = ' ';
@ -43,19 +48,28 @@ SSelectionData promptForScreencopySelection() {
static auto* const* PALLOWTOKENBYDEFAULT = static auto* const* PALLOWTOKENBYDEFAULT =
(Hyprlang::INT* const*)g_pPortalManager->m_sConfig.config->getConfigValuePtr("screencopy:allow_token_by_default")->getDataStaticPtr(); (Hyprlang::INT* const*)g_pPortalManager->m_sConfig.config->getConfigValuePtr("screencopy:allow_token_by_default")->getDataStaticPtr();
// DANGEROUS: we are sending a list of app IDs and titles via env. Make sure it's in 'singlequotes' to avoid something like $(rm -rf /) std::vector<std::string> args;
// TODO: this is dumb, use a pipe or something. if (**PALLOWTOKENBYDEFAULT)
std::string cmd = args.emplace_back("--allow-token");
std::format("WAYLAND_DISPLAY='{}' QT_QPA_PLATFORM='wayland' XCURSOR_SIZE='{}' HYPRLAND_INSTANCE_SIGNATURE='{}' XDPH_WINDOW_SHARING_LIST='{}' hyprland-share-picker{} 2>&1",
WAYLAND_DISPLAY ? WAYLAND_DISPLAY : "", XCURSOR_SIZE ? XCURSOR_SIZE : "24", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0", buildWindowList(),
(**PALLOWTOKENBYDEFAULT ? " --allow-token" : ""));
const auto RETVAL = execAndGet(cmd.c_str()); CProcess proc("hyprland-share-picker", args);
proc.addEnv("WAYLAND_DISPLAY", WAYLAND_DISPLAY ? WAYLAND_DISPLAY : "");
proc.addEnv("QT_QPA_PLATFORM", "wayland");
proc.addEnv("XCURSOR_SIZE", XCURSOR_SIZE ? XCURSOR_SIZE : "24");
proc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0");
proc.addEnv("XDPH_WINDOW_SHARING_LIST", buildWindowList()); // buildWindowList will sanitize any shell stuff in case the picker (qt) does something funky? It shouldn't.
if (!proc.runSync())
return data;
const auto RETVAL = proc.stdOut();
const auto RETVALERR = proc.stdErr();
if (!RETVAL.contains("[SELECTION]")) { if (!RETVAL.contains("[SELECTION]")) {
// failed // failed
constexpr const char* QPA_ERR = "qt.qpa.plugin: Could not find the Qt platform plugin";
if (RETVAL.contains("qt.qpa.plugin: Could not find the Qt platform plugin")) { if (RETVAL.contains(QPA_ERR) || RETVALERR.contains(QPA_ERR)) {
// prompt the user to install qt5-wayland and qt6-wayland // prompt the user to install qt5-wayland and qt6-wayland
addHyprlandNotification("3", 7000, "0", "[xdph] Could not open the picker: qt5-wayland or qt6-wayland doesn't seem to be installed."); addHyprlandNotification("3", 7000, "0", "[xdph] Could not open the picker: qt5-wayland or qt6-wayland doesn't seem to be installed.");
} }
@ -71,12 +85,11 @@ SSelectionData promptForScreencopySelection() {
const auto SEL = SELECTION.substr(SELECTION.find_first_of('/') + 1); const auto SEL = SELECTION.substr(SELECTION.find_first_of('/') + 1);
for (auto& flag : FLAGS) { for (auto& flag : FLAGS) {
if (flag == 'r') { if (flag == 'r')
data.allowToken = true; data.allowToken = true;
} else { else
Debug::log(LOG, "[screencopy] unknown flag from share-picker: {}", flag); Debug::log(LOG, "[screencopy] unknown flag from share-picker: {}", flag);
} }
}
if (SEL.find("screen:") == 0) { if (SEL.find("screen:") == 0) {
data.type = TYPE_OUTPUT; data.type = TYPE_OUTPUT;