Core: Blendfile Write: Check against multiple writing at same address.

Blendfile uses the 'old (memory) address' of its data as 'uid' in the
blendfile. There should only be one block written for a given address
and a same ID (each ID define its own 'virtual address space').

This commit checks that this condition is met at wrtite time (except for
undo steps, for performances reasons).

Tooling part of the investigations on #125001.
This commit is contained in:
Bastien Montagne
2024-07-26 12:16:42 +02:00
parent 2100623a96
commit 536fb53dc6

View File

@@ -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<const void *> 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);