Skip to content

Commit 6cc06d7

Browse files
committed
Bug#35983164 NdbProcess treats space, backslash, double quote different on Windows and posix [#2]
The posix version of NdbProcess::start_process assumed the arguments where quoted using " and \ in a way that resembles POSIX sh quoting, and unquoted spaces were treated as argument separators splitting the argument to several. But the Windows version of NdbProcess::start_process did not treat options in the same way. And the Windows C runtime (CRT) parse arguments different from POSIX sh. Note that if program do not use CRT when it may treat the command line in its own way and the quoting done for CRT will mess up the command line. On Windows NdbProcess:start_process should only be used for CRT compatible programs on Windows with respect to argument quoting on command line, or one should make sure given arguments will not trigger unwanted quoting. This may be relevant for ndb_sign_keys and --CA-tool=<batch-file>. Instead this patch change the intention of start_process to pass arguments without modification from caller to the called C programs argument vector in its main entry function. In posix path that is easy, just pass the incoming C strings to execvp. On Windows one need to quote for Windows CRT when composing the command line. Note that the command part of command line have different quoting than the following arguments have. Change-Id: I763530c634d3ea460b24e6e01061bbb5f3321ad4
1 parent 6c88e6a commit 6cc06d7

File tree

1 file changed

+83
-9
lines changed

1 file changed

+83
-9
lines changed

storage/ndb/include/portlib/NdbProcess.hpp

+83-9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
#include <array>
3030
#include <filesystem>
31+
#include <memory>
32+
#include <optional>
3133

3234
#include "util/BaseString.hpp"
3335
#include "util/require.h"
@@ -148,6 +150,20 @@ class NdbProcess {
148150

149151
NdbProcess(BaseString name, Pipes *fds) : m_name(name), m_pipes(fds) {}
150152

153+
/*
154+
* Quoting function to be used for passing program name and arguments to a
155+
* Windows program that follows the quoting of command line supported by
156+
* Microsoft C/C++ runtime. See for example description in:
157+
* https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args
158+
*
159+
* Note this quoting is not always suitable when calling other programs since
160+
* they are free to interpret the command line as they wish and the possible
161+
* quoting done may mess up. For example cmd.exe treats unquoted ^
162+
* differently.
163+
*/
164+
static std::optional<BaseString> quote_for_windows_crt(
165+
const char *str) noexcept;
166+
151167
static bool start_process(process_handle_t &pid, const char *path,
152168
const char *cwd, const Args &args, Pipes *pipes);
153169
};
@@ -225,6 +241,45 @@ inline void NdbProcess::Args::add(const Args &args) {
225241
for (unsigned i = 0; i < args.m_args.size(); i++) add(args.m_args[i].c_str());
226242
}
227243

244+
std::optional<BaseString> NdbProcess::quote_for_windows_crt(
245+
const char *str) noexcept {
246+
/*
247+
* Assuming program file names can not include " or end with a \ this
248+
* function should be usable also for quoting the command part of command
249+
* line when calling a C program via CreateProcess.
250+
*/
251+
const char *p = str;
252+
while (isprint(*p) && *p != ' ' && *p != '"' && *p != '*' && *p != '?') p++;
253+
if (*p == '\0' && str[0] != '\0') return str;
254+
BaseString ret;
255+
ret.append('"');
256+
size_t backslashes = 0;
257+
while (*str) {
258+
switch (*str) {
259+
case '"':
260+
if (backslashes) {
261+
// backslashes preceding double quote needs quoting
262+
ret.append(backslashes, '\\');
263+
backslashes = 0;
264+
}
265+
// use double double quotes to quote double quote
266+
ret.append('"');
267+
break;
268+
case '\\':
269+
// Count backslashes in case they will be followed by double quote
270+
backslashes++;
271+
break;
272+
default:
273+
backslashes = 0;
274+
}
275+
ret.append(*str);
276+
str++;
277+
}
278+
if (backslashes) ret.append(backslashes, '\\');
279+
ret.append('"');
280+
return ret;
281+
}
282+
228283
#ifdef _WIN32
229284

230285
/******
@@ -297,11 +352,27 @@ inline bool NdbProcess::start_process(process_handle_t &pid, const char *path,
297352
if (len > 0) path = full_path;
298353
}
299354

300-
BaseString cmdLine(path);
301-
BaseString argStr;
302-
argStr.assign(args.args(), " ");
303-
cmdLine.append(" ");
304-
cmdLine.append(argStr);
355+
std::optional<BaseString> quoted = quote_for_windows_crt(path);
356+
if (!quoted) {
357+
fprintf(stderr, "Function failed, could not quote command name: %s\n",
358+
path);
359+
return false;
360+
}
361+
BaseString cmdLine(quoted.value().c_str());
362+
363+
/* Quote each argument, and append it to the command line */
364+
auto &args_vec = args.args();
365+
for (size_t i = 0; i < args_vec.size(); i++) {
366+
auto *arg = args_vec[i].c_str();
367+
std::optional<BaseString> quoted = quote_for_windows_crt(arg);
368+
if (!quoted) {
369+
fprintf(stderr, "Function failed, could not quote command argument: %s\n",
370+
arg);
371+
return false;
372+
}
373+
cmdLine.append(' ');
374+
cmdLine.append(quoted.value().c_str());
375+
}
305376
char *command_line = strdup(cmdLine.c_str());
306377

307378
STARTUPINFO si;
@@ -451,11 +522,14 @@ inline bool NdbProcess::start_process(process_handle_t &pid, const char *path,
451522
}
452523
}
453524

454-
// Concatenate arguments
455-
BaseString args_str;
456-
args_str.assign(args.args(), " ");
525+
auto &args_vec = args.args();
526+
size_t arg_cnt = args_vec.size();
527+
char **argv = new char *[1 + arg_cnt + 1];
528+
argv[0] = const_cast<char *>(path);
529+
for (size_t i = 0; i < arg_cnt; i++)
530+
argv[1 + i] = const_cast<char *>(args_vec[i].c_str());
531+
argv[1 + arg_cnt] = nullptr;
457532

458-
char **argv = BaseString::argify(path, args_str.c_str());
459533
execvp(path, argv);
460534

461535
fprintf(stderr, "execv failed, error %d '%s'\n", errno, strerror(errno));

0 commit comments

Comments
 (0)