55#include < cstring>
66#include < memory>
77#include < vector>
8+ #include < filesystem>
9+ #include < random>
10+ #include < string>
11+ #include < sstream>
812
913namespace bb {
10- inline std::vector<uint8_t > exec_pipe ([[maybe_unused]] const std::string& command)
14+
15+ // Unsafe version - renamed to make it clear this is dangerous
16+ inline std::vector<uint8_t > exec_pipe_unsafe ([[maybe_unused]] const std::string& command)
1117{
1218#ifdef __wasm__
1319 throw_or_abort (" Can't use popen() in wasm! Implement this functionality natively." );
@@ -27,4 +33,101 @@ inline std::vector<uint8_t> exec_pipe([[maybe_unused]] const std::string& comman
2733 return output;
2834#endif
2935}
36+
37+ // Tag type to mark string literals as safe
38+ template <size_t N>
39+ struct literal_string {
40+ constexpr literal_string (const char (&str)[N]) {
41+ std::copy_n (str, N, value);
42+ }
43+ char value[N];
44+ };
45+
46+ // Helper to check if a type is a literal_string
47+ template <typename T>
48+ struct is_literal_string : std::false_type {};
49+
50+ template <size_t N>
51+ struct is_literal_string <literal_string<N>> : std::true_type {};
52+
53+ template <typename T>
54+ inline constexpr bool is_literal_string_v = is_literal_string<T>::value;
55+
56+ // Concept to ensure arguments are safe for command construction
57+ template <typename T>
58+ concept SafeCommandArg = std::is_arithmetic_v<std::decay_t <T>> || is_literal_string_v<std::decay_t <T>>;
59+
60+ // Safe exec_pipe_safe that builds command from literal strings and numbers
61+ template <SafeCommandArg... Args>
62+ inline std::vector<uint8_t > exec_pipe_safe (Args... args) {
63+ std::ostringstream command;
64+ auto append = [&command](auto && arg) {
65+ using T = std::decay_t <decltype (arg)>;
66+ if constexpr (std::is_arithmetic_v<T>) {
67+ command << arg;
68+ } else {
69+ command << arg.value ;
70+ }
71+ };
72+ (append (args), ...);
73+ return exec_pipe_unsafe (command.str ());
74+ }
75+
76+ // RAII class for creating temporary symlinks to safely pass file paths to shell commands
77+ class SafePathSymlink {
78+ std::filesystem::path symlink_path;
79+ bool created = false ;
80+
81+ static uint64_t get_random_suffix () {
82+ // Thread-local RNG for performance
83+ static thread_local std::random_device rd;
84+ static thread_local std::mt19937_64 gen (rd ());
85+ static thread_local std::uniform_int_distribution<uint64_t > dis (100000 , 99999999 );
86+ return dis (gen);
87+ }
88+
89+ public:
90+ explicit SafePathSymlink (const std::string& target_path) {
91+ // Generate random temporary symlink name
92+ std::filesystem::path temp_dir = std::filesystem::temp_directory_path ();
93+ std::string random_name = " bb_safe_" + std::to_string (get_random_suffix ());
94+ symlink_path = temp_dir / random_name;
95+
96+ // Create symlink
97+ std::error_code ec;
98+ std::filesystem::create_symlink (target_path, symlink_path, ec);
99+ if (ec) {
100+ throw_or_abort (" Failed to create temporary symlink: " + ec.message ());
101+ }
102+ created = true ;
103+ }
104+
105+ ~SafePathSymlink () {
106+ if (created) {
107+ std::error_code ec;
108+ std::filesystem::remove (symlink_path, ec);
109+ }
110+ }
111+
112+ // Delete copy and move operations - only needed as stack variable in exec_pipe_with_path
113+ SafePathSymlink (const SafePathSymlink&) = delete ;
114+ SafePathSymlink& operator =(const SafePathSymlink&) = delete ;
115+ SafePathSymlink (SafePathSymlink&&) = delete ;
116+ SafePathSymlink& operator =(SafePathSymlink&&) = delete ;
117+
118+ std::string path () const {
119+ return symlink_path.string ();
120+ }
121+ };
122+
123+ // Helper function to execute a command with a safe file path (requires literal string prefixes)
124+ template <size_t N1, size_t N2 = 1 >
125+ inline std::vector<uint8_t > exec_pipe_with_path (const char (&command_prefix)[N1],
126+ const std::string& file_path,
127+ const char (&command_suffix)[N2] = "") {
128+ SafePathSymlink safe_path (file_path);
129+ std::string command = std::string (command_prefix) + safe_path.path () + std::string (command_suffix);
130+ return exec_pipe_unsafe (command);
131+ }
132+
30133} // namespace bb
0 commit comments