fix(audit): security, correctness, refactors, docs (no wire change) #306
@@ -53,7 +53,7 @@ bool validate_config(const Config* config) {
|
||||
return false;
|
||||
}
|
||||
if (config->compression_threads > 0 && !config->use_compression) {
|
||||
log_message(LOG_LEVEL_ERROR, "--compress-threads requires compression (-c or -z)");
|
||||
log_message(LOG_LEVEL_ERROR, "--compress-threads requires compression (-z/--compress)");
|
||||
return false;
|
||||
}
|
||||
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
|
||||
|
||||
+22
-10
@@ -19,7 +19,9 @@ void print_usage(void) {
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -c, --checksum Verify content by checksum instead of size+mtime\n");
|
||||
printf(" -z, --compress [level] Enable compression (level 1-22, default 5)\n");
|
||||
printf(" -z, --compress [level] Enable compression. The default level is\n");
|
||||
printf(" per-codec: zstd 3 (range 1-22), zlib/zlibx 6, lz4\n");
|
||||
printf(" ignores the level\n");
|
||||
printf(" -a, --archive rsync archive mode (-rlptgoD): links, perms, times,\n");
|
||||
printf(" owner, group, devices and specials; not\n");
|
||||
printf(" compression/multithreading\n");
|
||||
@@ -36,8 +38,8 @@ void print_usage(void) {
|
||||
printf(" arguments, e.g. -e \"ssh -p 2222\"\n");
|
||||
printf(" --rsync-path <path> Alias for --fastsync-server-path (path to the\n");
|
||||
printf(" fastsync server binary on the remote side)\n");
|
||||
printf(" --blocking-io Leave the SSH transport socket without read/write\n");
|
||||
printf(" timeouts so it blocks naturally\n");
|
||||
printf(" --blocking-io SSH transport only: leave the socket without read/write\n");
|
||||
printf(" timeouts so it blocks naturally (no effect on TCP)\n");
|
||||
printf(" --outbuf=MODE stdout/stderr buffering: N (none/unbuffered),\n");
|
||||
printf(" L (line-buffered), or B (block-buffered, default)\n");
|
||||
printf(" --progress Show transfer progress\n");
|
||||
@@ -49,6 +51,7 @@ void print_usage(void) {
|
||||
printf(" converted before transmission and back on receipt; a\n");
|
||||
printf(" name that cannot be represented in the target charset\n");
|
||||
printf(" fails that transfer cleanly (rsync-compatible)\n");
|
||||
printf(" --no-iconv Disable --iconv charset conversion (same as --iconv=-)\n");
|
||||
printf(" --protocol=NUM Force the wire protocol version (must equal the current\n");
|
||||
printf(" PROTOCOL_VERSION; FastSync cannot speak older/virtual\n");
|
||||
printf(" wire formats)\n");
|
||||
@@ -192,6 +195,10 @@ void print_usage(void) {
|
||||
printf(" -U, --atimes Preserve access times\n");
|
||||
printf(" -N, --crtimes Capture birth time; cannot be applied (documented\n");
|
||||
printf(" divergence)\n");
|
||||
printf(" -O, --omit-dir-times Do not apply modification times to directories\n");
|
||||
printf(" -J, --omit-link-times Do not apply times to symlinks\n");
|
||||
printf(" --open-noatime Open source files with O_NOATIME so reading for a\n");
|
||||
printf(" transfer does not update their access time\n");
|
||||
printf(" -X, --xattrs Preserve user extended attributes (user.* only;\n");
|
||||
printf(" privileged security.*/trusted.* namespaces are\n");
|
||||
printf(" never captured or applied)\n");
|
||||
@@ -287,6 +294,10 @@ void print_usage(void) {
|
||||
printf(" -x, --one-file-system Do not cross filesystem boundaries\n");
|
||||
printf(" --log-file <path>, --log-file=<path> Write log messages to file\n");
|
||||
printf(" --stderr=MODE Route logging to stderr: errors or all\n");
|
||||
printf(" --msgs2stderr Route all messages to stderr (deprecated spelling of\n");
|
||||
printf(" --stderr=all)\n");
|
||||
printf(" --no-msgs2stderr Select errors-only stderr (deprecated spelling; the\n");
|
||||
printf(" default)\n");
|
||||
printf(" --partial Keep partial files on interrupted transfer\n");
|
||||
printf(" --partial-dir <dir> Directory for partial files (implies --partial)\n");
|
||||
printf(" -T, --temp-dir <dir> Scratch dir for temp files before atomic install.\n");
|
||||
@@ -337,7 +348,8 @@ void print_usage(void) {
|
||||
printf(" --append-verify Like --append, but verifies the retained prefix checksum\n");
|
||||
printf(" before appending (falls back to a full transfer on mismatch)\n");
|
||||
printf(" --fsync Fsync every written file before publication\n");
|
||||
printf(" --compress-level <n> Compression level (default: 5)\n");
|
||||
printf(" --compress-level <n> Compression level (per-codec default: zstd 3,\n");
|
||||
printf(" zlib/zlibx 6, lz4 ignores it)\n");
|
||||
printf(" --zl <n> Alias for --compress-level\n");
|
||||
printf(" --skip-compress=LIST Skip compression for suffixes in LIST (separated by\n");
|
||||
printf(" '/' as in rsync, or ','); a leading dot is optional. The\n");
|
||||
@@ -349,19 +361,19 @@ void print_usage(void) {
|
||||
}
|
||||
|
||||
void print_debug_usage(void) {
|
||||
printf("Emitting debug flags: IO,PROTO,PACK,UTIL,ALL,NONE\n");
|
||||
printf("Emitting debug flags: IO,PROTO,PACK,UTIL,FLIST,DEL,HASH,DELTASUM,\n");
|
||||
printf("RECV,FILTER,SEND,ALL,NONE\n");
|
||||
printf("Also accepted for rsync CLI parity (silent): ACL,BACKUP,BIND,CHDIR,\n");
|
||||
printf("CONNECT,CMD,DEL,DELTASUM,DUP,EXIT,FILTER,FLIST,FUZZY,GENR,HASH,HLINK,\n");
|
||||
printf("ICONV,NSTR,OWN,RECV,SEND,TIME.\n");
|
||||
printf("CONNECT,CMD,DUP,EXIT,FUZZY,GENR,HLINK,ICONV,NSTR,OWN,TIME.\n");
|
||||
printf("Flags may be comma-separated, for example: --debug=io,proto\n");
|
||||
printf("An optional level suffix is accepted (e.g. --debug=io2); level 0\n");
|
||||
printf("silences that item. Unknown names are rejected.\n");
|
||||
}
|
||||
|
||||
void print_info_usage(void) {
|
||||
printf("Emitting info flags: COPY,NAME,MISC,SKIP,STATS,ALL,NONE\n");
|
||||
printf("Also accepted for rsync CLI parity (silent): BACKUP,DEL,FLIST,MOUNT,\n");
|
||||
printf("NONREG,PROGRESS,REMOVE,SYMSAFE.\n");
|
||||
printf("Emitting info flags: COPY,MISC,SKIP,STATS,DEL,REMOVE,NAME,FLIST,\n");
|
||||
printf("NONREG,PROGRESS,MOUNT,ALL,NONE\n");
|
||||
printf("Also accepted for rsync CLI parity (silent): BACKUP,SYMS,SYMSAFE.\n");
|
||||
printf("Flags may be comma-separated, for example: --info=name,stats\n");
|
||||
printf("An optional level suffix is accepted (e.g. --info=stats2); level 0\n");
|
||||
printf("silences that item. Unknown names are rejected.\n");
|
||||
|
||||
+64
-20
@@ -1329,35 +1329,78 @@ static void test_parse_args_delete_timing_without_delete_rejected() {
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* Parsed-but-unimplemented options must fail instead of being silently accepted. */
|
||||
static void test_parse_args_rejects_unimplemented_options() {
|
||||
static const char* const options[] = {"--silent",
|
||||
"--queue-size",
|
||||
"-A",
|
||||
"--acls",
|
||||
"-X",
|
||||
"--xattrs",
|
||||
"-D",
|
||||
"--devices",
|
||||
"--delete-excluded",
|
||||
"--max-delete",
|
||||
"--prune-empty-dirs",
|
||||
"--bind-address",
|
||||
"--daemon",
|
||||
"--config",
|
||||
"--server"};
|
||||
/* Truly-unknown options (including server-only spellings) must be rejected
|
||||
* through the unknown-option path instead of being silently accepted. */
|
||||
static void test_parse_args_rejects_unknown_options() {
|
||||
static const char* const options[] = {"--silent", "--queue-size", "--bind-address",
|
||||
"--daemon", "--config", "--server"};
|
||||
|
||||
for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
|
||||
Config* cfg = config_create();
|
||||
char* argv[] = {"fastsync", (char*)options[i], "dummy", "/src", "/dst"};
|
||||
char* argv[] = {"fastsync", (char*)options[i], "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
|
||||
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), -1);
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), -1);
|
||||
config_delete(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Options that are genuinely implemented must parse successfully and record
|
||||
* their effect, rather than being lumped in with the unknown-option set. */
|
||||
static void test_parse_args_accepts_implemented_metadata_options() {
|
||||
Config* cfg = config_create();
|
||||
char* argv_x[] = {"fastsync", "-X", "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_x, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->preserve_xattrs);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_acls[] = {"fastsync", "--acls", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_acls, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->preserve_acls);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_d[] = {"fastsync", "-D", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_d, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->preserve_devices);
|
||||
EXPECT_TRUE(cfg->preserve_specials);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_devices[] = {"fastsync", "--devices", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_devices, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->preserve_devices);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_delete_excluded[] = {"fastsync", "--delete-excluded", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_delete_excluded, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->delete_excluded);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_max_delete[] = {"fastsync", "--max-delete=5", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_max_delete, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->max_delete, 5);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* argv_prune[] = {"fastsync", "--prune-empty-dirs", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_prune, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->prune_empty_dirs);
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* Test both rsync-compatible quiet spellings and option ordering. */
|
||||
static void test_parse_args_quiet() {
|
||||
static const char* const options[][2] = {
|
||||
@@ -4908,7 +4951,8 @@ void test_client_cli() {
|
||||
test_parse_args_delete_default_timing_and_commit();
|
||||
test_parse_args_delete_timing_conflict_rejected();
|
||||
test_parse_args_delete_timing_without_delete_rejected();
|
||||
test_parse_args_rejects_unimplemented_options();
|
||||
test_parse_args_rejects_unknown_options();
|
||||
test_parse_args_accepts_implemented_metadata_options();
|
||||
test_parse_args_quiet();
|
||||
test_parse_args_human_readable();
|
||||
test_parse_args_hard_links();
|
||||
|
||||
Reference in New Issue
Block a user