symlink-trust: -k/--copy-dirlinks, -K/--keep-dirlinks, --munge-links (+real -l/--links)
CI / lint (pull_request) Successful in 1m1s
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / build-and-test (pull_request) Successful in 1m19s
CI / lint (pull_request) Successful in 1m1s
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / build-and-test (pull_request) Successful in 1m19s
Adds symlink-target transmission (File is_symlink+symlink_target, STATUS_SYMLINK frame, chunk type 2), munge-links sender containment + receiver-side symmetric target containment, keep-dirlinks confined dir-symlink following (O_NOFOLLOW realpath-rechecked), and fixes -l to copy symlinks as symlinks. munge_links + keep_dirlinks cross the wire; copy_dirlinks client-only. PROTOCOL_VERSION 2.12.0->2.13.0. Review fixes: receiver rejects absolute/.. targets, gated unmunge, -K O_NOFOLLOW+re-fstat, keep_dirlinks set once at config-accept, rel_buf overflow fails the walk.
This commit is contained in:
@@ -468,6 +468,50 @@ static void test_file_write_to_disk_does_not_follow_symlink() {
|
||||
unlink(link);
|
||||
}
|
||||
|
||||
static void test_file_symlink_helpers() {
|
||||
/* Munge/unmunge round-trip restores the original target. */
|
||||
char* munged = file_symlink_munge("target.txt");
|
||||
EXPECT_NOT_NULL(munged);
|
||||
EXPECT_EQ_INT(memcmp(munged, SYMLINK_MUNGE_PREFIX, strlen(SYMLINK_MUNGE_PREFIX)), 0);
|
||||
EXPECT_TRUE(file_symlink_unmunge(munged));
|
||||
EXPECT_EQ_STR(munged, "target.txt");
|
||||
free(munged);
|
||||
|
||||
char noop[] = "plain-target";
|
||||
EXPECT_FALSE(file_symlink_unmunge(noop));
|
||||
EXPECT_EQ_STR(noop, "plain-target");
|
||||
|
||||
/* Containment: relative targets without ".." are safe; absolute or
|
||||
".."-escaping targets are not. */
|
||||
EXPECT_TRUE(file_symlink_target_contained("a.txt"));
|
||||
EXPECT_TRUE(file_symlink_target_contained("sub/dir/file"));
|
||||
EXPECT_FALSE(file_symlink_target_contained("/etc/passwd"));
|
||||
EXPECT_FALSE(file_symlink_target_contained("../escape"));
|
||||
EXPECT_FALSE(file_symlink_target_contained("a/../b"));
|
||||
EXPECT_FALSE(file_symlink_target_contained(""));
|
||||
}
|
||||
|
||||
static void test_file_symlink_at_secure() {
|
||||
const char* link = "test_symlink_at_secure_link";
|
||||
const char* outside = "test_symlink_at_secure_outside.txt";
|
||||
unlink(link);
|
||||
unlink(outside);
|
||||
EXPECT_TRUE(file_write_to_disk(outside, "out", 3, false, false));
|
||||
|
||||
EXPECT_TRUE(file_symlink_at_secure(link, "outside.text"));
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(lstat(link, &st), 0);
|
||||
EXPECT_TRUE(S_ISLNK(st.st_mode));
|
||||
|
||||
/* Replacing an existing non-directory entry is fine. */
|
||||
EXPECT_TRUE(file_symlink_at_secure(link, "other.txt"));
|
||||
EXPECT_EQ_INT(lstat(link, &st), 0);
|
||||
EXPECT_TRUE(S_ISLNK(st.st_mode));
|
||||
|
||||
unlink(link);
|
||||
unlink(outside);
|
||||
}
|
||||
|
||||
static void test_file_content_to_buffer() {
|
||||
const char* content = "Buffer content test";
|
||||
EXPECT_TRUE(file_write_to_disk("test_buffer_file.txt", content, strlen(content), false, false));
|
||||
@@ -978,6 +1022,8 @@ void test_file() {
|
||||
test_file_write_to_disk_creates_dirs();
|
||||
test_file_write_to_disk_does_not_follow_symlink();
|
||||
test_file_content_to_buffer();
|
||||
test_file_symlink_helpers();
|
||||
test_file_symlink_at_secure();
|
||||
test_file_save_to_disk_path_traversal();
|
||||
test_file_save_to_disk_deep_traversal();
|
||||
test_dir_entry_save_to_disk();
|
||||
|
||||
Reference in New Issue
Block a user