security(shared): fix -K TOCTOU, ssh old-args quoting, TLS opts, secret-file perms, sparse dedup
- file: open -K dirlink referents via a race-safe relative O_NOFOLLOW walk from the authorized-root fd instead of re-opening an absolute realpath() result (removes the intermediate-symlink swap TOCTOU). - transport_ssh: always single-quote the server path, including --old-args, so no mode can inject shell metacharacters. - transport_tls: set SSL_OP_NO_COMPRESSION and (guarded) SSL_OP_NO_RENEGOTIATION. - credentials: reject --password-file/--early-input with any group/other permission bit; chmod 0600 the affected test fixtures. - file_store: export file_store_write_sparse() and remove the verbatim file.c duplicate.
This commit is contained in:
+30
-40
@@ -84,32 +84,29 @@ char* ssh_build_remote_command(const char* server_path, bool old_args, char* con
|
||||
const char* suffix = " --stdio";
|
||||
|
||||
/* Each --remote-option=OPT is appended after " --stdio" as one shell word,
|
||||
escaped with the SAME single-quote boundary used for the server path. This
|
||||
stays safe even in --old-args mode (which leaves the server path unquoted):
|
||||
remote options are always single-quoted individually, so a value containing
|
||||
shell metacharacters (; & | ` $ ()) can never break out of the quoting to
|
||||
inject an unrelated remote command. Values are already validated at CLI
|
||||
parse time (non-empty, no control characters); this layer only adds the
|
||||
escaping boundary. */
|
||||
escaped with the SAME single-quote boundary used for the server path, so a
|
||||
value containing shell metacharacters (; & | ` $ ()) can never break out of
|
||||
the quoting to inject an unrelated remote command. Values are already
|
||||
validated at CLI parse time (non-empty, no control characters); this layer
|
||||
only adds the escaping boundary. */
|
||||
size_t path_len = strlen(path);
|
||||
size_t suffix_len = strlen(suffix);
|
||||
|
||||
/* The base command (server path, quoted unless --old-args, then " --stdio"). */
|
||||
size_t command_len;
|
||||
if (old_args) {
|
||||
if (path_len > SIZE_MAX - suffix_len - 1)
|
||||
return NULL;
|
||||
command_len = path_len + suffix_len + 1;
|
||||
} else {
|
||||
size_t quote_count = 0;
|
||||
for (const char* p = path; *p; p++)
|
||||
if (*p == '\'')
|
||||
quote_count++;
|
||||
if (path_len > SIZE_MAX - suffix_len - 4 ||
|
||||
quote_count > (SIZE_MAX - path_len - suffix_len - 4) / 4)
|
||||
return NULL;
|
||||
command_len = path_len + quote_count * 4 + suffix_len + 4;
|
||||
}
|
||||
/* The base command: the server path is ALWAYS quoted as one single-quoted
|
||||
shell word (remote options below reuse the same escaping), then
|
||||
" --stdio". Quoting the path is the only injection-safe construction: an
|
||||
unquoted path would carry shell metacharacters straight into the remote
|
||||
shell command. --old-args is kept for CLI/ABI compatibility but no longer
|
||||
disables that protection. */
|
||||
(void)old_args;
|
||||
size_t quote_count = 0;
|
||||
for (const char* p = path; *p; p++)
|
||||
if (*p == '\'')
|
||||
quote_count++;
|
||||
if (path_len > SIZE_MAX - suffix_len - 4 ||
|
||||
quote_count > (SIZE_MAX - path_len - suffix_len - 4) / 4)
|
||||
return NULL;
|
||||
size_t command_len = path_len + quote_count * 4 + suffix_len + 4;
|
||||
|
||||
/* Add each remote option, escaped as one single-quoted word:
|
||||
" '<body>'", i.e. 1 leading space + 1 open quote + body (len + 3 per
|
||||
@@ -141,25 +138,18 @@ char* ssh_build_remote_command(const char* server_path, bool old_args, char* con
|
||||
if (!command)
|
||||
return NULL;
|
||||
char* out = command;
|
||||
if (old_args) {
|
||||
memcpy(out, path, path_len);
|
||||
out += path_len;
|
||||
memcpy(out, suffix, suffix_len + 1);
|
||||
out += suffix_len;
|
||||
} else {
|
||||
*out++ = '\'';
|
||||
for (const char* p = path; *p; p++) {
|
||||
if (*p == '\'') {
|
||||
memcpy(out, "'\\''", 4);
|
||||
out += 4;
|
||||
} else {
|
||||
*out++ = *p;
|
||||
}
|
||||
*out++ = '\'';
|
||||
for (const char* p = path; *p; p++) {
|
||||
if (*p == '\'') {
|
||||
memcpy(out, "'\\''", 4);
|
||||
out += 4;
|
||||
} else {
|
||||
*out++ = *p;
|
||||
}
|
||||
*out++ = '\'';
|
||||
memcpy(out, suffix, suffix_len + 1);
|
||||
out += suffix_len;
|
||||
}
|
||||
*out++ = '\'';
|
||||
memcpy(out, suffix, suffix_len + 1);
|
||||
out += suffix_len;
|
||||
for (int i = 0; i < remote_option_count; i++) {
|
||||
const char* opt = remote_options[i];
|
||||
*out++ = ' ';
|
||||
|
||||
Reference in New Issue
Block a user