diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index 51a6d44a2ef..37857dd1798 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -96,6 +96,7 @@ #include "BLI_linklist.h" #include "BLI_math_base.h" #include "BLI_mempool.h" +#include "BLI_set.hh" #include "BLI_threads.h" #include "MEM_guardedalloc.h" /* MEM_freeN */ @@ -416,8 +417,22 @@ struct WriteData { size_t write_len; #endif - /** Set on unlikely case of an error (ignores further file writing). */ - bool error; + /** Whether writefile code is currently writing an ID. */ + bool is_writing_id; + + /** Some validation and error handling data. */ + struct { + /** + * Set on unlikely case of an error (ignores further file writing). Only used for very + * low-level errors (like if the actual write on file fails). + */ + bool critical_error; + /** + * A set of all 'old' adresses used as uid of written blocks for the current ID. Allows + * detecting invalid re-uses of the same address multiple times. + */ + blender::Set per_id_addresses_set; + } validation_data; /** #MemFile writing (used for undo). */ MemFileWriteData mem; @@ -461,7 +476,7 @@ static WriteData *writedata_new(WriteWrap *ww) static void writedata_do_write(WriteData *wd, const void *mem, size_t memlen) { - if ((wd == nullptr) || wd->error || (mem == nullptr) || memlen < 1) { + if ((wd == nullptr) || wd->validation_data.critical_error || (mem == nullptr) || memlen < 1) { return; } @@ -470,7 +485,7 @@ static void writedata_do_write(WriteData *wd, const void *mem, size_t memlen) return; } - if (UNLIKELY(wd->error)) { + if (UNLIKELY(wd->validation_data.critical_error)) { return; } @@ -480,7 +495,7 @@ static void writedata_do_write(WriteData *wd, const void *mem, size_t memlen) } else { if (!wd->ww->write(mem, memlen)) { - wd->error = true; + wd->validation_data.critical_error = true; } } } @@ -518,7 +533,7 @@ static void mywrite_flush(WriteData *wd) */ static void mywrite(WriteData *wd, const void *adr, size_t len) { - if (UNLIKELY(wd->error)) { + if (UNLIKELY(wd->validation_data.critical_error)) { return; } @@ -601,7 +616,7 @@ static bool mywrite_end(WriteData *wd) BLO_memfile_write_finalize(&wd->mem); } - const bool err = wd->error; + const bool err = wd->validation_data.critical_error; writedata_free(wd); return err; @@ -614,6 +629,11 @@ static bool mywrite_end(WriteData *wd) */ static void mywrite_id_begin(WriteData *wd, ID *id) { + BLI_assert(wd->is_writing_id == false); + wd->is_writing_id = true; + + BLI_assert(wd->validation_data.per_id_addresses_set.is_empty()); + if (wd->use_memfile) { wd->mem.current_id_session_uid = id->session_uid; @@ -652,6 +672,11 @@ static void mywrite_id_end(WriteData *wd, ID * /*id*/) mywrite_flush(wd); wd->mem.current_id_session_uid = MAIN_ID_SESSION_UID_UNSET; } + + wd->validation_data.per_id_addresses_set.clear(); + + BLI_assert(wd->is_writing_id == true); + wd->is_writing_id = false; } /** \} */ @@ -660,6 +685,32 @@ static void mywrite_id_end(WriteData *wd, ID * /*id*/) /** \name Generic DNA File Writing * \{ */ +/** + * Return \a false if the given 'old' address is not valid in current context. The block should + * not be written in that case. + * + * \note Currently only checks that #BLO_CODE_DATA blocks written as part of an ID data never match + * an already written one for the same ID. + */ +static bool write_at_address_validate(WriteData *wd, int filecode, const void *address) +{ + /* Skip in undo case. */ + if (wd->use_memfile) { + return true; + } + + if (wd->is_writing_id && filecode == BLO_CODE_DATA) { + if (wd->validation_data.per_id_addresses_set.contains(address)) { + CLOG_ERROR(&LOG, + "Same identifier (old address) used several times for a same ID, skipping this " + "block to avoid critical corruption of the Blender file."); + return false; + } + wd->validation_data.per_id_addresses_set.add(address); + } + return true; +} + static void writestruct_at_address_nr( WriteData *wd, int filecode, const int struct_nr, int nr, const void *adr, const void *data) { @@ -671,6 +722,10 @@ static void writestruct_at_address_nr( return; } + if (!write_at_address_validate(wd, filecode, adr)) { + return; + } + /* Initialize #BHead. */ bh.code = filecode; bh.old = adr; @@ -706,6 +761,10 @@ static void writedata(WriteData *wd, int filecode, size_t len, const void *adr) return; } + if (!write_at_address_validate(wd, filecode, adr)) { + return; + } + /* Align to 4 (writes uninitialized bytes in some cases). */ len = (len + 3) & ~size_t(3);