fix(audit): security, correctness, refactors, docs (no wire change) #306
+1
-6
@@ -226,11 +226,6 @@ static void release_authorization(void) {
|
||||
close(root_fd);
|
||||
}
|
||||
|
||||
static bool path_is_within(const char* root, const char* path) {
|
||||
size_t n = strlen(root);
|
||||
return strncmp(root, path, n) == 0 && (path[n] == '\0' || path[n] == '/');
|
||||
}
|
||||
|
||||
/* --mkpath contract: when the client's destination root directory does not
|
||||
exist yet on the server side, --mkpath tells the server to create it (and
|
||||
any missing leading components) below the authorized root at connection
|
||||
@@ -790,7 +785,7 @@ void handler(int file_descriptor) {
|
||||
if (joined_destination)
|
||||
destination = joined_destination;
|
||||
if (!destination || has_path_traversal(destination) ||
|
||||
!path_is_within(authorized_root, destination)) {
|
||||
!path_is_within_root(authorized_root, destination)) {
|
||||
log_message(LOG_LEVEL_ERROR, "Rejected destination outside authorized root");
|
||||
free(joined_destination);
|
||||
joined_destination = NULL;
|
||||
|
||||
@@ -3,20 +3,12 @@
|
||||
#include "credentials.h"
|
||||
#include "utils.h"
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
static void set_error(char* err, size_t err_size, const char* fmt, ...) {
|
||||
if (!err || err_size == 0)
|
||||
return;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_size, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
#define set_error utils_set_error
|
||||
|
||||
void server_cli_options_default(ServerCliOptions* opts) {
|
||||
if (!opts)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/params.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -58,14 +57,7 @@ struct CredentialStore {
|
||||
static const uint8_t k_dummy_stored_key[CREDENTIAL_KEY_LEN] = {0};
|
||||
static const uint8_t k_dummy_server_key[CREDENTIAL_KEY_LEN] = {0};
|
||||
|
||||
static void set_error(char* err, size_t err_size, const char* fmt, ...) {
|
||||
if (!err || err_size == 0)
|
||||
return;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_size, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
#define set_error utils_set_error
|
||||
|
||||
static bool is_comment_char(char c) {
|
||||
return c == '#' || c == ';';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -17,14 +16,7 @@
|
||||
/* helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void set_error(char* err, size_t err_size, const char* fmt, ...) {
|
||||
if (!err || err_size == 0)
|
||||
return;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_size, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
#define set_error utils_set_error
|
||||
|
||||
/* Trim leading and trailing ASCII space/tab in place; returns the new start. */
|
||||
static char* trim_ws(char* s) {
|
||||
|
||||
+2
-17
@@ -4,22 +4,12 @@
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Write a diagnostic message into the caller's optional buffer. A NULL `err`
|
||||
* (or a zero size) is a no-op, so a caller that only needs the boolean status
|
||||
* may pass NULL without the snprintf-on-NULL undefined behaviour. */
|
||||
static void filter_set_error(char* err, size_t err_size, const char* fmt, ...) {
|
||||
if (!err || err_size == 0)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(err, err_size, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
/* Write a diagnostic message into the caller's optional buffer. */
|
||||
#define filter_set_error utils_set_error
|
||||
|
||||
/* ---- Ordered rule lists ---- */
|
||||
|
||||
@@ -870,8 +860,3 @@ FilterAction filter_rules_apply_side(const FilterRuleList* list, const char* rel
|
||||
}
|
||||
return FILTER_ACTION_NONE;
|
||||
}
|
||||
|
||||
FilterAction filter_rules_apply(const FilterRuleList* list, const char* rel_path, const char* leaf,
|
||||
bool is_dir) {
|
||||
return filter_rules_apply_side(list, rel_path, leaf, is_dir, FILTER_SIDE_SENDER);
|
||||
}
|
||||
|
||||
@@ -131,9 +131,4 @@ FilterRuleList* filter_file_read(const char* dir_path, const char* owner_rel, bo
|
||||
FilterAction filter_rules_apply_side(const FilterRuleList* list, const char* rel_path,
|
||||
const char* leaf, bool is_dir, unsigned side);
|
||||
|
||||
/* Sender-side convenience wrapper (kept for callers/tests that only need the
|
||||
* transfer decision). */
|
||||
FilterAction filter_rules_apply(const FilterRuleList* list, const char* rel_path, const char* leaf,
|
||||
bool is_dir);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -48,6 +49,15 @@ const char* utils_get_authorized_root_path(void) {
|
||||
return authorized_root_path;
|
||||
}
|
||||
|
||||
void utils_set_error(char* err, size_t err_size, const char* fmt, ...) {
|
||||
if (!err || err_size == 0)
|
||||
return;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_size, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
bool path_is_within_root(const char* root, const char* path) {
|
||||
size_t root_len = strlen(root);
|
||||
return strncmp(root, path, root_len) == 0 && (path[root_len] == '\0' || path[root_len] == '/');
|
||||
|
||||
@@ -225,6 +225,11 @@ void utils_set_authorized_root_fd(int fd);
|
||||
* threads spawn; see utils.c). */
|
||||
int utils_get_authorized_root_fd(void);
|
||||
const char* utils_get_authorized_root_path(void);
|
||||
/* Write a diagnostic message into a caller-supplied buffer, mirroring
|
||||
* vsnprintf. A NULL `err` or a zero `err_size` is a no-op, so a caller that
|
||||
* only needs the boolean status may safely pass NULL. Returns nothing; the
|
||||
* buffer is always NUL-terminated by vsnprintf when err_size > 0. */
|
||||
void utils_set_error(char* err, size_t err_size, const char* fmt, ...);
|
||||
/* True when `path` is `root` itself or lies directly beneath it: a lexical
|
||||
* prefix test requiring the byte after `root` to be '\0' or '/'. Both `root`
|
||||
* and `path` must be absolute canonical paths free of "."/".." components (the
|
||||
|
||||
+16
-8
@@ -179,8 +179,10 @@ static void test_filter_rules_apply_supported_modifiers() {
|
||||
const char* texts[] = {"- *.tmp"};
|
||||
FilterRuleList* list = filter_base_build(texts, 1, false, false, NULL, 0);
|
||||
EXPECT_NOT_NULL(list);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "b.tmp", "b.tmp", false), FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "a.txt", "a.txt", false), FILTER_ACTION_NONE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "b.tmp", "b.tmp", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "a.txt", "a.txt", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_NONE);
|
||||
filter_rule_list_free(list);
|
||||
}
|
||||
/* anchored include then exclude-all */
|
||||
@@ -188,8 +190,10 @@ static void test_filter_rules_apply_supported_modifiers() {
|
||||
const char* texts[] = {"+ /a.txt", "- *"};
|
||||
FilterRuleList* list = filter_base_build(texts, 2, false, false, NULL, 0);
|
||||
EXPECT_NOT_NULL(list);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "a.txt", "a.txt", false), FILTER_ACTION_INCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "b.txt", "b.txt", false), FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "a.txt", "a.txt", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_INCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "b.txt", "b.txt", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_EXCLUDE);
|
||||
filter_rule_list_free(list);
|
||||
}
|
||||
/* negate */
|
||||
@@ -197,8 +201,10 @@ static void test_filter_rules_apply_supported_modifiers() {
|
||||
const char* texts[] = {"-! *.o"};
|
||||
FilterRuleList* list = filter_base_build(texts, 1, false, false, NULL, 0);
|
||||
EXPECT_NOT_NULL(list);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "foo.c", "foo.c", false), FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "foo.o", "foo.o", false), FILTER_ACTION_NONE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "foo.c", "foo.c", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "foo.o", "foo.o", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_NONE);
|
||||
filter_rule_list_free(list);
|
||||
}
|
||||
/* dir-only trailing slash */
|
||||
@@ -206,8 +212,10 @@ static void test_filter_rules_apply_supported_modifiers() {
|
||||
const char* texts[] = {"+ dir/", "- *"};
|
||||
FilterRuleList* list = filter_base_build(texts, 2, false, false, NULL, 0);
|
||||
EXPECT_NOT_NULL(list);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "dir", "dir", true), FILTER_ACTION_INCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply(list, "dir", "dir", false), FILTER_ACTION_EXCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "dir", "dir", true, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_INCLUDE);
|
||||
EXPECT_EQ_INT(filter_rules_apply_side(list, "dir", "dir", false, FILTER_SIDE_SENDER),
|
||||
FILTER_ACTION_EXCLUDE);
|
||||
filter_rule_list_free(list);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user