From e8a3d5fa16f3bd24c3fb708f73404624bfabd682 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 12:27:32 +0200 Subject: [PATCH] Bound avatar transfers and enforce immutable hashed uploads --- src/member_files/member_files.h | 1 + src/member_files/storage.c | 8 ++++++++ src/member_files/transfer.c | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/member_files/member_files.h b/src/member_files/member_files.h index f2e3033f..c1ce2f6b 100644 --- a/src/member_files/member_files.h +++ b/src/member_files/member_files.h @@ -30,6 +30,7 @@ void member_files_gui_request(void* arg); struct mf_transfer_request { struct UTUN_INSTANCE* inst; uint64_t id, group, server, owner; + uint64_t max_bytes; // 0 — без лимита; проверяется до открытия локального файла и передачи данных. int upload; uint8_t version[16]; char remote[MF_PATH_MAX + 1], local[MF_PATH_MAX + 1]; diff --git a/src/member_files/storage.c b/src/member_files/storage.c index d6ac9653..81ae0608 100644 --- a/src/member_files/storage.c +++ b/src/member_files/storage.c @@ -317,6 +317,12 @@ static struct store_upload* store_upload_find(struct mf_storage* s, uint64_t gro static int store_begin(struct mf_storage* s, uint64_t group, uint64_t peer, const char* path, const struct mf_message* q) { if (q->size > INT64_MAX || mf_zero(q->upload, 16)) return MF_INVALID_RANGE; + if (!strncmp(q->path, "avatars/", 8)) { + if (!mf_avatar_file_valid(q->path + 8) || !q->size || q->size > MF_AVATAR_MAX_BYTES) return MF_INVALID_RANGE; + if (!mf_zero(q->version, 16)) return MF_EXISTS; + char hash[65]; for (unsigned i = 0; i < 32; i++) snprintf(hash + i * 2, 3, "%02x", q->hash[i]); + if (strncmp(q->path + 8, hash, 64)) return MF_HASH_MISMATCH; + } struct store_upload* u = store_upload_find(s, group, peer, q->upload); if (u) { if (strcmp(u->path, path) || u->size != q->size || memcmp(u->hash, q->hash, 32) || memcmp(u->expected, q->version, 16)) @@ -377,6 +383,8 @@ 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; 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/src/member_files/transfer.c b/src/member_files/transfer.c index 27bfc1b8..25130c06 100644 --- a/src/member_files/transfer.c +++ b/src/member_files/transfer.c @@ -125,7 +125,8 @@ static void transfer_work(void* arg) { } else if (t->sending) { rc = mf_disk_local_open(t->request.local, 0, &t->fd); if (!rc) rc = mf_disk_info(t->fd, &t->source); - if (!rc && (t->source.type != MF_FILE || t->source.size > INT64_MAX)) rc = MF_INVALID_RANGE; + if (!rc && (t->source.type != MF_FILE || t->source.size > INT64_MAX || + (t->request.max_bytes && t->source.size > t->request.max_bytes))) rc = MF_INVALID_RANGE; if (!rc) { t->io_size = t->source.size; rc = mf_disk_hash(t->fd, t->io_size, t->hash); } struct mf_disk_info after; if (!rc) rc = mf_disk_info(t->fd, &after); @@ -345,6 +346,11 @@ static void transfer_response(void* arg, const struct mf_message* r) { if (r->status) { transfer_stop(t, r->status); return; } if (r->command == MF_STAT) { if (r->type != MF_FILE || r->size > INT64_MAX || mf_zero(r->version, 16)) { transfer_stop(t, MF_INVALID_RANGE); return; } + if (t->request.max_bytes && r->size > t->request.max_bytes) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: download exceeds limit id=%llu bytes=%llu limit=%llu", + (unsigned long long)t->request.id, (unsigned long long)r->size, (unsigned long long)t->request.max_bytes); + transfer_stop(t, MF_INVALID_RANGE); return; + } t->size = r->size; memcpy(t->version, r->version, 16); transfer_submit(t, TRANSFER_PREPARE); } else if (r->size != t->size || r->cursor != t->request.id || memcmp(r->upload, t->upload, 16)) { transfer_stop(t, MF_BAD_PACKET);