Skip to content

Commit

Permalink
Process: if exec(3) fails with "Argument list too long", truncate the…
Browse files Browse the repository at this point in the history
… input

starting with the longest allowed suffix until it's small enough.
This way no string gets discarted completely in favor of a longer one before it.
Also, the computation happens only if necessary and in the already fork(2)ed child process.
The pragmatic implementation doesn't even try to mimic kernels' limits perfectly.
  • Loading branch information
Al2Klimov committed Nov 9, 2023
1 parent 76b460c commit f7e73f3
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 14 deletions.
115 changes: 102 additions & 13 deletions lib/base/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
#include "base/utility.hpp"
#include "base/scriptglobal.hpp"
#include "base/json.hpp"
#include <cstddef>
#include <boost/algorithm/string/join.hpp>
#include <boost/thread/once.hpp>
#include <thread>
#include <iostream>
#include <utility>

#ifndef _WIN32
# include <errno.h>
# include <execvpe.h>
# include <poll.h>
# include <string.h>
Expand Down Expand Up @@ -48,9 +51,9 @@ static pid_t l_ProcessControlPID;
static boost::once_flag l_ProcessOnceFlag = BOOST_ONCE_INIT;
static boost::once_flag l_SpawnHelperOnceFlag = BOOST_ONCE_INIT;

Process::Process(Process::Arguments arguments, Dictionary::Ptr extraEnvironment)
Process::Process(Process::Arguments arguments, Dictionary::Ptr extraEnvironment, Array::Ptr safeToTruncate)
: m_Arguments(std::move(arguments)), m_ExtraEnvironment(std::move(extraEnvironment)),
m_Timeout(600)
m_SafeToTruncate(std::move(safeToTruncate)), m_Timeout(600)
#ifdef _WIN32
, m_ReadPending(false), m_ReadFailed(false), m_Overlapped()
#else /* _WIN32 */
Expand Down Expand Up @@ -84,6 +87,7 @@ static Value ProcessSpawnImpl(struct msghdr *msgh, const Dictionary::Ptr& reques

Array::Ptr arguments = request->Get("arguments");
Dictionary::Ptr extraEnvironment = request->Get("extraEnvironment");
Array::Ptr safeToTruncate = request->Get("safeToTruncate");
bool adjustPriority = request->Get("adjustPriority");

// build argv
Expand Down Expand Up @@ -136,6 +140,40 @@ static Value ProcessSpawnImpl(struct msghdr *msgh, const Dictionary::Ptr& reques

extraEnvironment.reset();

std::vector<std::pair<const char*, size_t>> safeToTruncateStrings;
std::vector<char*> safeToTruncateArgs;

if (safeToTruncate) {
// Lock here recursively, not in the fork(2)ed child
ObjectLock lock (safeToTruncate);

safeToTruncateStrings.reserve(safeToTruncate->GetLength());

for (auto& v : safeToTruncate) {
if (v.GetType() == ValueString) {
auto s (v.Get<String>().CStr());
auto len (strlen(s)); // exec(3) uses C strings, so we do

if (len >= 1024u) { // Better safe than sorry
safeToTruncateStrings.emplace_back(s, len);
}
}
}
}

if (!safeToTruncateStrings.empty()) {
decltype(safeToTruncateArgs)::size_type totalArgs = 0;

for (auto strings : {argv, envp}) {
for (auto s (strings); *s; ++s) {
++totalArgs;
}
}

// malloc(3) here, not in the fork(2)ed child
safeToTruncateArgs.reserve(totalArgs);
}

pid_t pid = fork();

int errorCode = 0;
Expand Down Expand Up @@ -174,16 +212,65 @@ static Value ProcessSpawnImpl(struct msghdr *msgh, const Dictionary::Ptr& reques
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, nullptr);

if (icinga2_execvpe(argv[0], argv, envp) < 0) {
char errmsg[512];
strcpy(errmsg, "execvpe(");
strncat(errmsg, argv[0], sizeof(errmsg) - strlen(errmsg) - 1);
strncat(errmsg, ") failed", sizeof(errmsg) - strlen(errmsg) - 1);
errmsg[sizeof(errmsg) - 1] = '\0';
perror(errmsg);
}
for (;;) {
if (icinga2_execvpe(argv[0], argv, envp) < 0) {
if (errno == E2BIG && !safeToTruncateStrings.empty()) {
if (safeToTruncateArgs.empty()) {
// Initialize safeToTruncateArgs
for (auto strings : {argv, envp}) {
for (auto it (strings); *it; ++it) {
auto s (*it);
auto len (strlen(s));

for (auto suffix : safeToTruncateStrings) {
if (suffix.second <= len) {
auto substr (s + len - suffix.second);

// Allow truncating an arg only if it ends with a safe to truncate string
if (!strcmp(substr, suffix.first)) {
// Allow truncating only the safe part of a string,
// e.g. "-foo=bar" => "-foo=", but not "-foo=bar" => "-f"
safeToTruncateArgs.emplace(substr);
}
}
}
}
}
}

_exit(128);
char* longest = nullptr;
size_t longestLen = 0;

for (auto s : safeToTruncateArgs) {
if (longest) {
auto len (strlen(s));

if (len > longestLen) {
longest = s;
longestLen = len;
}
} else {
longest = s;
longestLen = strlen(longest);
}
}

if (longest) {
longest[longestLen * 3u / 4u] = 0;
continue; // Re-try
}
}

char errmsg[512];
strcpy(errmsg, "execvpe(");
strncat(errmsg, argv[0], sizeof(errmsg) - strlen(errmsg) - 1);
strncat(errmsg, ") failed", sizeof(errmsg) - strlen(errmsg) - 1);
errmsg[sizeof(errmsg) - 1] = '\0';
perror(errmsg);
}

_exit(128);
}
}

(void)close(fds[0]);
Expand Down Expand Up @@ -367,12 +454,14 @@ static void StartSpawnProcessHelper()
l_ProcessControlPID = pid;
}

static pid_t ProcessSpawn(const std::vector<String>& arguments, const Dictionary::Ptr& extraEnvironment, bool adjustPriority, int fds[3])
static pid_t ProcessSpawn(const std::vector<String>& arguments, const Dictionary::Ptr& extraEnvironment,
const Array::Ptr& safeToTruncate, bool adjustPriority, int fds[3])
{
Dictionary::Ptr request = new Dictionary({
{ "command", "spawn" },
{ "arguments", Array::FromVector(arguments) },
{ "extraEnvironment", extraEnvironment },
{ "safeToTruncate", safeToTruncate },
{ "adjustPriority", adjustPriority }
});

Expand Down Expand Up @@ -980,7 +1069,7 @@ void Process::Run(const std::function<void(const ProcessResult&)>& callback)
fds[1] = outfds[1];
fds[2] = outfds[1];

m_Process = ProcessSpawn(m_Arguments, m_ExtraEnvironment, m_AdjustPriority, fds);
m_Process = ProcessSpawn(m_Arguments, m_ExtraEnvironment, m_SafeToTruncate, m_AdjustPriority, fds);
m_PID = m_Process;

if (m_PID == -1) {
Expand Down
3 changes: 2 additions & 1 deletion lib/base/process.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Process final : public Object

static const std::deque<Process::Ptr>::size_type MaxTasksPerThread = 512;

Process(Arguments arguments, Dictionary::Ptr extraEnvironment = nullptr);
Process(Arguments arguments, Dictionary::Ptr extraEnvironment = nullptr, Array::Ptr safeToTruncate = nullptr);
~Process() override;

void SetTimeout(double timeout);
Expand Down Expand Up @@ -80,6 +80,7 @@ class Process final : public Object
private:
Arguments m_Arguments;
Dictionary::Ptr m_ExtraEnvironment;
Array::Ptr m_SafeToTruncate;

double m_Timeout;
#ifndef _WIN32
Expand Down

0 comments on commit f7e73f3

Please sign in to comment.