From 80c81502fb69859145f1b4a993597c45726fc27d Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 12:53:05 +0200 Subject: [PATCH] Protect avatar paths across platforms and initialize join fixture nodes --- src/member_files/storage.c | 13 +++++++++++-- tests/test_chat_join_e2e.c | 5 ++++- tests/test_member_files.c | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/member_files/storage.c b/src/member_files/storage.c index 81ae0608..3b2a7503 100644 --- a/src/member_files/storage.c +++ b/src/member_files/storage.c @@ -5,6 +5,7 @@ #include "../../lib/u_async.h" #include #include +#include #include #include @@ -14,6 +15,13 @@ #define RECEIPT_TB 1200000ULL #define STORE_USERS 256 +// Windows сравнивает имена без регистра; алиас не должен обходить неизменяемость avatars. +static int store_avatar_namespace(const char* path) { + const char name[] = "avatars"; + for (unsigned i = 0; i < sizeof(name) - 1; i++) if (tolower((unsigned char)path[i]) != name[i]) return 0; + return !path[7] || path[7] == '/'; +} + struct store_file { char path[MF_PATH_MAX + 80]; int fd; uint64_t touched; }; struct store_user { struct store_user* next; uint64_t owner, used, reserved, files, directories; int valid; }; struct store_upload { @@ -383,8 +391,9 @@ static int store_begin(struct mf_storage* s, uint64_t group, uint64_t peer, cons static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, const struct mf_message* q, struct mf_message* r) { if (!mf_path_valid(q->path, q->command == MF_LIST || q->command == MF_STAT || q->command == MF_QUOTA) || (q->command == MF_RENAME && !mf_path_valid(q->target, 0))) return MF_INVALID_PATH; - if (q->command == MF_RENAME && (!strcmp(q->path, "avatars") || !strncmp(q->path, "avatars/", 8) || - !strcmp(q->target, "avatars") || !strncmp(q->target, "avatars/", 8))) return MF_INVALID_PATH; + if (store_avatar_namespace(q->path) && strcmp(q->path, "avatars") && + (strncmp(q->path, "avatars/", 8) || !mf_avatar_file_valid(q->path + 8))) return MF_INVALID_PATH; + if (q->command == MF_RENAME && (store_avatar_namespace(q->path) || store_avatar_namespace(q->target))) return MF_INVALID_PATH; char path[MF_PATH_MAX + 80]; snprintf(path, sizeof(path), "%llu/%llu%s%s", (unsigned long long)group, (unsigned long long)q->owner, *q->path ? "/" : "", q->path); struct mf_disk_info info = {0}; diff --git a/tests/test_chat_join_e2e.c b/tests/test_chat_join_e2e.c index d4e72afb..2153238c 100644 --- a/tests/test_chat_join_e2e.c +++ b/tests/test_chat_join_e2e.c @@ -308,7 +308,10 @@ static void member_put_self(struct UTUN_INSTANCE* inst, const char* name) { } static void member_put_placeholder(struct UTUN_INSTANCE* inst, int idx, const char* name) { - (void)name; + /* Ключи fixture известны родителю; плейсхолдер не создаёт общую запись узла сам. */ + if (topo_node_sqlite_node_update_verified(inst->topo_sqlite_db, g_sh.nid[idx], name, g_sh.x_pub[idx], g_sh.ed_pub[idx], + (uint64_t)ntp_time_get_seconds(inst), 0) < 0) + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "test: cannot initialize fixture node=%016llx", (unsigned long long)g_sh.nid[idx]); if (topo_node_sqlite_member_placeholder_put(inst->topo_sqlite_db, g_sh.ch_id, g_sh.nid[idx], g_sh.x_pub[idx], g_sh.ed_pub[idx]) < 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "test: cannot create member placeholder node=%016llx", (unsigned long long)g_sh.nid[idx]); } diff --git a/tests/test_member_files.c b/tests/test_member_files.c index 258117f7..c4c95749 100644 --- a/tests/test_member_files.c +++ b/tests/test_member_files.c @@ -54,6 +54,8 @@ static void avatar_storage_test(const char* directory) { q.size = MF_AVATAR_MAX_BYTES + 1; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); q.size = 0; run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_RANGE); memcpy(q.version, version, 16); snprintf(q.target, sizeof(q.target), "renamed.png"); run(s, 55, 77, MF_RENAME, MF_INVALID_PATH); + snprintf(q.path, sizeof(q.path), "Avatars/%s.png", hash); run(s, 55, 77, MF_BEGIN_WRITE, MF_INVALID_PATH); + snprintf(q.path, sizeof(q.path), "AVATARS"); run(s, 55, 77, MF_MKDIR, MF_INVALID_PATH); memset(&q, 0, sizeof(q)); q.owner = 77; run(s, 55, 88, MF_QUOTA, MF_OK); EXPECT(mf_get64(r.data) == sizeof(contents) && mf_get64(r.data + 16) == 0 && mf_get64(r.data + 24) == 1 && mf_get64(r.data + 32) == 1, "avatars counted once in UID quota");