* [PATCH 0/2] qom: Tighten object lifetime handling
@ 2026-06-15 4:11 Akihiko Odaki
2026-06-15 4:11 ` [PATCH 1/2] qom: Reject temporary object resurrection Akihiko Odaki
2026-06-15 4:11 ` [PATCH 2/2] qom: Manage references to embedded child objects Akihiko Odaki
0 siblings, 2 replies; 17+ messages in thread
From: Akihiko Odaki @ 2026-06-15 4:11 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé, Manos Pitsidianakis,
qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia,
Kevin Wolf, Hanna Reitz, qemu-block, Akihiko Odaki
QOM has extensive assertions and prevents most cases that would trigger
undefined behavior, but two corner cases still need explicit handling:
temporary object resurrection during finalization, and references to
embedded objects. This series covers both cases.
The temporary object resurrection patch originated from:
https://lore.kernel.org/qemu-devel/20250906-mr-v2-1-2820f5a3d282@rsg.ci.i.u-tokyo.ac.jp/
("[PATCH v2 1/3] qom: Do not finalize twice")
I also plan to make the embedded object reference handling a
prerequisite for the next version of the following series:
https://lore.kernel.org/qemu-devel/20250917-qom-v1-0-7262db7b0a84@rsg.ci.i.u-tokyo.ac.jp/
("[PATCH 00/35] memory: QOM-ify AddressSpace")
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
Akihiko Odaki (2):
qom: Reject temporary object resurrection
qom: Manage references to embedded child objects
include/qom/object.h | 39 ++++++++++++++++-----
block/throttle-groups.c | 2 +-
qom/object.c | 75 +++++++++++++++++++++++++++++++----------
tests/unit/check-qom-proplist.c | 8 ++---
rust/qom/src/qom.rs | 8 ++---
5 files changed, 98 insertions(+), 34 deletions(-)
---
base-commit: 2db91528542672cf0db78b3f2cc0e22b36302b38
change-id: 20260614-embedded-00ca98fcdb5c
Best regards,
--
Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 1/2] qom: Reject temporary object resurrection 2026-06-15 4:11 [PATCH 0/2] qom: Tighten object lifetime handling Akihiko Odaki @ 2026-06-15 4:11 ` Akihiko Odaki 2026-06-15 8:14 ` Daniel P. Berrangé 2026-06-15 8:34 ` Kevin Wolf 2026-06-15 4:11 ` [PATCH 2/2] qom: Manage references to embedded child objects Akihiko Odaki 1 sibling, 2 replies; 17+ messages in thread From: Akihiko Odaki @ 2026-06-15 4:11 UTC (permalink / raw) To: qemu-devel Cc: Paolo Bonzini, Daniel P. Berrangé, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block, Akihiko Odaki If object_ref() is called during finalization, it will temporarily "resurrect" the object. Although object_finalize() asserts that no resurrecting reference remains before freeing the object, the assertion cannot catch the case where the resurrecting reference is dropped before freeing the object. One way to catch this would be to add a check in object_ref() to reject resurrecting references before they are created. However, object_ref() is frequently called so it is better to minimize the overhead. To avoid adding the overhead, change how the reference count is represented and let an existing assertion detect resurrection. More concretely, obj->ref now stores the current reference count minus 1. The stored count is therefore 0 after object_initialize(), and the final object_unref() decrements it from 0 to UINT32_MAX and starts finalization. A resurrecting object_ref() will then trip the existing obj->ref < INT_MAX assertion. Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> --- include/qom/object.h | 4 ++-- block/throttle-groups.c | 2 +- qom/object.c | 15 ++++++--------- tests/unit/check-qom-proplist.c | 8 ++++---- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/qom/object.h b/include/qom/object.h index 11f55613fcd0..c4dcf65dcf7f 100644 --- a/include/qom/object.h +++ b/include/qom/object.h @@ -1121,7 +1121,7 @@ GSList *object_class_get_list_sorted(const char *implements_type, * @obj: the object * * Increase the reference count of a object. A object cannot be freed as long - * as its reference count is greater than zero. + * as a reference remains. * Returns: @obj */ Object *object_ref(void *obj); @@ -1131,7 +1131,7 @@ Object *object_ref(void *obj); * @obj: the object * * Decrease the reference count of a object. A object cannot be freed as long - * as its reference count is greater than zero. + * as a reference remains. */ void object_unref(void *obj); diff --git a/block/throttle-groups.c b/block/throttle-groups.c index 4b1b1944c20a..3b2a89f5849d 100644 --- a/block/throttle-groups.c +++ b/block/throttle-groups.c @@ -962,7 +962,7 @@ static void throttle_group_get_limits(Object *obj, Visitor *v, static bool throttle_group_can_be_deleted(UserCreatable *uc) { - return OBJECT(uc)->ref == 1; + return OBJECT(uc)->ref == 0; } static void throttle_group_obj_class_init(ObjectClass *klass, diff --git a/qom/object.c b/qom/object.c index 0ac201de4c15..761eff1337e6 100644 --- a/qom/object.c +++ b/qom/object.c @@ -497,7 +497,6 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type memset(obj, 0, type->instance_size); obj->class = type->class; - object_ref(obj); object_class_property_init_all(obj); obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, object_property_free); @@ -560,11 +559,10 @@ bool object_initialize_child_with_propsv(Object *parentobj, out: /* - * We want @obj's reference to be 1 on success, 0 on failure. - * On success, it's 2: one taken by object_initialize(), and one - * by object_property_add_child(). - * On failure in object_initialize() or earlier, it's 1. - * On failure afterwards, it's also 1: object_unparent() releases + * We want @obj's reference to be 0 on success, UINT32_MAX on failure. + * On success, it's 1: one taken by object_property_add_child(). + * On failure in object_initialize() or earlier, it's 0. + * On failure afterwards, it's also 0: object_unparent() releases * the reference taken by object_property_add_child(). */ object_unref(obj); @@ -668,7 +666,6 @@ static void object_finalize(void *data) object_property_del_all(obj); object_deinit(obj, ti); - g_assert(obj->ref == 0); g_assert(obj->parent == NULL); if (obj->free) { obj->free(obj); @@ -1329,10 +1326,10 @@ void object_unref(void *objptr) if (!obj) { return; } - g_assert(obj->ref > 0); + g_assert(obj->ref < INT_MAX); /* parent always holds a reference to its children */ - if (qatomic_fetch_dec(&obj->ref) == 1) { + if (qatomic_fetch_dec(&obj->ref) == 0) { object_finalize(obj); } } diff --git a/tests/unit/check-qom-proplist.c b/tests/unit/check-qom-proplist.c index 89de92b7d91f..b5527dd52afd 100644 --- a/tests/unit/check-qom-proplist.c +++ b/tests/unit/check-qom-proplist.c @@ -351,7 +351,7 @@ static void test_dummy_createv_tree(void) NULL)); g_assert(err == NULL); - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); g_assert(dobj->bv == true); g_assert(dobj->av == DUMMY_PLATYPUS); @@ -375,7 +375,7 @@ static void test_dummy_createv_parentless(void) NULL)); g_assert(err == NULL); - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); g_assert(dobj->bv == true); g_assert(dobj->av == DUMMY_PLATYPUS); @@ -414,7 +414,7 @@ static void test_dummy_createlist_tree(void) NULL)); g_assert(err == NULL); - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); g_assert(dobj->bv == true); g_assert(dobj->av == DUMMY_PLATYPUS); @@ -450,7 +450,7 @@ static void test_dummy_createlist_parentless(void) NULL)); g_assert(err == NULL); - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); g_assert(dobj->bv == true); g_assert(dobj->av == DUMMY_PLATYPUS); -- 2.54.0 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 1/2] qom: Reject temporary object resurrection 2026-06-15 4:11 ` [PATCH 1/2] qom: Reject temporary object resurrection Akihiko Odaki @ 2026-06-15 8:14 ` Daniel P. Berrangé 2026-06-15 8:34 ` Kevin Wolf 1 sibling, 0 replies; 17+ messages in thread From: Daniel P. Berrangé @ 2026-06-15 8:14 UTC (permalink / raw) To: Akihiko Odaki Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, Jun 15, 2026 at 01:11:06PM +0900, Akihiko Odaki wrote: > If object_ref() is called during finalization, it will temporarily > "resurrect" the object. Although object_finalize() asserts that no > resurrecting reference remains before freeing the object, the assertion > cannot catch the case where the resurrecting reference is dropped before > freeing the object. What's the real world problem here ? > > One way to catch this would be to add a check in object_ref() to reject > resurrecting references before they are created. However, object_ref() > is frequently called so it is better to minimize the overhead. > > To avoid adding the overhead, change how the reference count is > represented and let an existing assertion detect resurrection. More > concretely, obj->ref now stores the current reference count minus 1. The > stored count is therefore 0 after object_initialize(), and the final > object_unref() decrements it from 0 to UINT32_MAX and starts > finalization. A resurrecting object_ref() will then trip the existing > obj->ref < INT_MAX assertion. Please no, this is horribly surprising semantics. > > Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> > --- > include/qom/object.h | 4 ++-- > block/throttle-groups.c | 2 +- > qom/object.c | 15 ++++++--------- > tests/unit/check-qom-proplist.c | 8 ++++---- > 4 files changed, 13 insertions(+), 16 deletions(-) > > diff --git a/include/qom/object.h b/include/qom/object.h > index 11f55613fcd0..c4dcf65dcf7f 100644 > --- a/include/qom/object.h > +++ b/include/qom/object.h > @@ -1121,7 +1121,7 @@ GSList *object_class_get_list_sorted(const char *implements_type, > * @obj: the object > * > * Increase the reference count of a object. A object cannot be freed as long > - * as its reference count is greater than zero. > + * as a reference remains. > * Returns: @obj > */ > Object *object_ref(void *obj); > @@ -1131,7 +1131,7 @@ Object *object_ref(void *obj); > * @obj: the object > * > * Decrease the reference count of a object. A object cannot be freed as long > - * as its reference count is greater than zero. > + * as a reference remains. > */ > void object_unref(void *obj); > > diff --git a/block/throttle-groups.c b/block/throttle-groups.c > index 4b1b1944c20a..3b2a89f5849d 100644 > --- a/block/throttle-groups.c > +++ b/block/throttle-groups.c > @@ -962,7 +962,7 @@ static void throttle_group_get_limits(Object *obj, Visitor *v, > > static bool throttle_group_can_be_deleted(UserCreatable *uc) > { > - return OBJECT(uc)->ref == 1; > + return OBJECT(uc)->ref == 0; > } > > static void throttle_group_obj_class_init(ObjectClass *klass, > diff --git a/qom/object.c b/qom/object.c > index 0ac201de4c15..761eff1337e6 100644 > --- a/qom/object.c > +++ b/qom/object.c > @@ -497,7 +497,6 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type > > memset(obj, 0, type->instance_size); > obj->class = type->class; > - object_ref(obj); > object_class_property_init_all(obj); > obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal, > NULL, object_property_free); > @@ -560,11 +559,10 @@ bool object_initialize_child_with_propsv(Object *parentobj, > > out: > /* > - * We want @obj's reference to be 1 on success, 0 on failure. > - * On success, it's 2: one taken by object_initialize(), and one > - * by object_property_add_child(). > - * On failure in object_initialize() or earlier, it's 1. > - * On failure afterwards, it's also 1: object_unparent() releases > + * We want @obj's reference to be 0 on success, UINT32_MAX on failure. > + * On success, it's 1: one taken by object_property_add_child(). > + * On failure in object_initialize() or earlier, it's 0. > + * On failure afterwards, it's also 0: object_unparent() releases > * the reference taken by object_property_add_child(). > */ > object_unref(obj); > @@ -668,7 +666,6 @@ static void object_finalize(void *data) > object_property_del_all(obj); > object_deinit(obj, ti); > > - g_assert(obj->ref == 0); > g_assert(obj->parent == NULL); > if (obj->free) { > obj->free(obj); > @@ -1329,10 +1326,10 @@ void object_unref(void *objptr) > if (!obj) { > return; > } > - g_assert(obj->ref > 0); > + g_assert(obj->ref < INT_MAX); > > /* parent always holds a reference to its children */ > - if (qatomic_fetch_dec(&obj->ref) == 1) { > + if (qatomic_fetch_dec(&obj->ref) == 0) { > object_finalize(obj); > } > } > diff --git a/tests/unit/check-qom-proplist.c b/tests/unit/check-qom-proplist.c > index 89de92b7d91f..b5527dd52afd 100644 > --- a/tests/unit/check-qom-proplist.c > +++ b/tests/unit/check-qom-proplist.c > @@ -351,7 +351,7 @@ static void test_dummy_createv_tree(void) > NULL)); > > g_assert(err == NULL); > - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); > + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); > g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); > g_assert(dobj->bv == true); > g_assert(dobj->av == DUMMY_PLATYPUS); > @@ -375,7 +375,7 @@ static void test_dummy_createv_parentless(void) > NULL)); > > g_assert(err == NULL); > - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); > + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); > g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); > g_assert(dobj->bv == true); > g_assert(dobj->av == DUMMY_PLATYPUS); > @@ -414,7 +414,7 @@ static void test_dummy_createlist_tree(void) > NULL)); > > g_assert(err == NULL); > - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); > + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); > g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); > g_assert(dobj->bv == true); > g_assert(dobj->av == DUMMY_PLATYPUS); > @@ -450,7 +450,7 @@ static void test_dummy_createlist_parentless(void) > NULL)); > > g_assert(err == NULL); > - g_assert_cmpint(dobj->parent_obj.ref, ==, 1); > + g_assert_cmpint(dobj->parent_obj.ref, ==, 0); > g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss"); > g_assert(dobj->bv == true); > g_assert(dobj->av == DUMMY_PLATYPUS); > > -- > 2.54.0 > With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :| ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/2] qom: Reject temporary object resurrection 2026-06-15 4:11 ` [PATCH 1/2] qom: Reject temporary object resurrection Akihiko Odaki 2026-06-15 8:14 ` Daniel P. Berrangé @ 2026-06-15 8:34 ` Kevin Wolf 2026-06-15 8:50 ` Akihiko Odaki 1 sibling, 1 reply; 17+ messages in thread From: Kevin Wolf @ 2026-06-15 8:34 UTC (permalink / raw) To: Akihiko Odaki Cc: qemu-devel, Paolo Bonzini, Daniel P. Berrangé, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Hanna Reitz, qemu-block Am 15.06.2026 um 06:11 hat Akihiko Odaki geschrieben: > If object_ref() is called during finalization, it will temporarily > "resurrect" the object. Although object_finalize() asserts that no > resurrecting reference remains before freeing the object, the assertion > cannot catch the case where the resurrecting reference is dropped before > freeing the object. Ok, and why is that a problem? In the cover letter, you referred to an earlier patch you had posted: The temporary object resurrection patch originated from: https://lore.kernel.org/qemu-devel/20250906-mr-v2-1-2820f5a3d282@rsg.ci.i.u-tokyo.ac.jp/ ("[PATCH v2 1/3] qom: Do not finalize twice") From that patch, it's clear that one problem is that .finalize() can be called multiple times, which you probably don't want. But what is the reason for switching from just ignoring the second .finalize() like in the old patch to completely forbidding a temporary refcount increase? > One way to catch this would be to add a check in object_ref() to reject > resurrecting references before they are created. However, object_ref() > is frequently called so it is better to minimize the overhead. We're talking about a single comparison with zero here. So if that's the worse alternative, the other one can't have any drawbacks. > To avoid adding the overhead, change how the reference count is > represented and let an existing assertion detect resurrection. More > concretely, obj->ref now stores the current reference count minus 1. The > stored count is therefore 0 after object_initialize(), and the final > object_unref() decrements it from 0 to UINT32_MAX and starts > finalization. A resurrecting object_ref() will then trip the existing > obj->ref < INT_MAX assertion. A refcount field where 0 means that a last reference is still remaining is highly confusing. If we do want to forbid taking a temporary new reference in .finalize() for some reason, the explicit check in object_ref() sounds much better to me. > Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> > --- > include/qom/object.h | 4 ++-- > block/throttle-groups.c | 2 +- > qom/object.c | 15 ++++++--------- > tests/unit/check-qom-proplist.c | 8 ++++---- > 4 files changed, 13 insertions(+), 16 deletions(-) Kevin ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/2] qom: Reject temporary object resurrection 2026-06-15 8:34 ` Kevin Wolf @ 2026-06-15 8:50 ` Akihiko Odaki 0 siblings, 0 replies; 17+ messages in thread From: Akihiko Odaki @ 2026-06-15 8:50 UTC (permalink / raw) To: Kevin Wolf Cc: qemu-devel, Paolo Bonzini, Daniel P. Berrangé, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Hanna Reitz, qemu-block On 2026/06/15 17:34, Kevin Wolf wrote: > Am 15.06.2026 um 06:11 hat Akihiko Odaki geschrieben: >> If object_ref() is called during finalization, it will temporarily >> "resurrect" the object. Although object_finalize() asserts that no >> resurrecting reference remains before freeing the object, the assertion >> cannot catch the case where the resurrecting reference is dropped before >> freeing the object. > > Ok, and why is that a problem? > > In the cover letter, you referred to an earlier patch you had posted: > > The temporary object resurrection patch originated from: > https://lore.kernel.org/qemu-devel/20250906-mr-v2-1-2820f5a3d282@rsg.ci.i.u-tokyo.ac.jp/ > ("[PATCH v2 1/3] qom: Do not finalize twice") > > From that patch, it's clear that one problem is that .finalize() can be > called multiple times, which you probably don't want. But what is the > reason for switching from just ignoring the second .finalize() like in > the old patch to completely forbidding a temporary refcount increase? None. I thought merely skipping finalization is not a well-defined semantics (e.g., breaking Rust safety guarantee), but, reconsidering now, I think that's fine. > >> One way to catch this would be to add a check in object_ref() to reject >> resurrecting references before they are created. However, object_ref() >> is frequently called so it is better to minimize the overhead. > > We're talking about a single comparison with zero here. So if that's the > worse alternative, the other one can't have any drawbacks. True. > >> To avoid adding the overhead, change how the reference count is >> represented and let an existing assertion detect resurrection. More >> concretely, obj->ref now stores the current reference count minus 1. The >> stored count is therefore 0 after object_initialize(), and the final >> object_unref() decrements it from 0 to UINT32_MAX and starts >> finalization. A resurrecting object_ref() will then trip the existing >> obj->ref < INT_MAX assertion. > > A refcount field where 0 means that a last reference is still remaining > is highly confusing. If we do want to forbid taking a temporary new > reference in .finalize() for some reason, the explicit check in > object_ref() sounds much better to me. The rationale here is that the field is mostly contained in qom/object.c so the awkward semantics won't have a wide impact. But it's just "mostly" and there are two exceptions: block/throttle-groups.c and tests/unit/check-qom-proplist.c. I withdraw this patch for the drawbacks discussed above. Regards, Akihiko Odaki ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 4:11 [PATCH 0/2] qom: Tighten object lifetime handling Akihiko Odaki 2026-06-15 4:11 ` [PATCH 1/2] qom: Reject temporary object resurrection Akihiko Odaki @ 2026-06-15 4:11 ` Akihiko Odaki 2026-06-15 8:12 ` Daniel P. Berrangé 2026-06-15 10:59 ` Daniel P. Berrangé 1 sibling, 2 replies; 17+ messages in thread From: Akihiko Odaki @ 2026-06-15 4:11 UTC (permalink / raw) To: qemu-devel Cc: Paolo Bonzini, Daniel P. Berrangé, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block, Akihiko Odaki Problem ======= The Rust wrapper for object_ref() is marked unsafe and has: > The object must not be embedded in another unless the outer > object is guaranteed to have a longer lifetime. In other words, object_ref() does not work for embedded objects and does not keep embedded objects alive. MemoryRegion has its own memory_region_ref() helper to call object_ref() on its owner for this reason. However, this is insufficient to avoid calling object_ref() for all embedded objects. For example, consider an embedded Device that has a MemoryRegion. When referencing a MemoryRegion for guest memory access, QEMU automatically references the owning Device to keep the MemoryRegion alive. However, that reference is ineffective if the Device itself is embedded, because object_ref() does not keep the containing storage alive. One concrete case is qemu-xhci: XHCIPciState embeds XHCIState as a child object, and the embedded XHCIState owns the MemoryRegion used for PCI BAR 0. When memory core references that region, memory_region_ref() references the embedded XHCIState owner, but that does not keep the containing XHCIPciState storage alive across runtime unplug. To avoid such a use-after-free hazard, memory_region_init*() would also need a SAFETY comment saying "the owner must not be embedded in another unless its outer object is guaranteed to have a longer lifetime." The unsafe nature of object_ref() propagates across the codebase and spreads SAFETY comments across the codebase, and any failure to fulfill the requirement can cause use-after-free. Solution ======== Eliminate this whole class of use-after-free hazard by properly managing references to the "storage" of embedded child objects. Objects now have a separate storage reference counter for their storage. If an object is embedded, the reference counter is set to UINT32_MAX. Otherwise, it represents the number of references to the object's storage - 1. Object::free is now a union with Object::storage, which holds a reference to the storage for an embedded object. This forms the following reference graph: Parent -> Storage Parent -> Embedded Child -> Storage Functions for taking and dropping references to object storage are also exposed as APIs. This is particularly useful when the storage needs to be freed asynchronously due to RCU, for example. A possible concern is that this adds a uint32_t to Object. This is not a problem in most situations where the host uses 64-bit addressing because the member is added to a gap needed for alignment. It does increase memory usage for 32-bit hosts, but proliferation of SAFETY comments to avoid the overhead is unlikely to be worthwhile. Note that the Rust wrapper for object_ref() is still marked as unsafe. This is because an object implemented in C may have pointers whose lifetimes are not properly extended according to the reference counter. Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> --- include/qom/object.h | 39 +++++++++++++++++++++++++++------- qom/object.c | 60 +++++++++++++++++++++++++++++++++++++++++++++------- rust/qom/src/qom.rs | 8 +++---- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/include/qom/object.h b/include/qom/object.h index c4dcf65dcf7f..b5d656e3758c 100644 --- a/include/qom/object.h +++ b/include/qom/object.h @@ -155,8 +155,12 @@ struct Object { /* private: */ ObjectClass *class; - ObjectFree *free; + union { + ObjectFree *free; + Object *storage; + }; GHashTable *properties; + uint32_t storage_ref; uint32_t ref; Object *parent; }; @@ -613,7 +617,7 @@ struct InterfaceClass * @klass: The class to instantiate. * * This function will initialize a new object using heap allocated memory. - * The returned object has a reference count of 1, and will be freed when + * The returned object has a reference count of 1, and will be finalized when * the last reference is dropped. * * Returns: The newly allocated and instantiated object. @@ -625,7 +629,7 @@ Object *object_new_with_class(ObjectClass *klass); * @typename: The name of the type of the object to instantiate. * * This function will initialize a new object using heap allocated memory. - * The returned object has a reference count of 1, and will be freed when + * The returned object has a reference count of 1, and will be finalized when * the last reference is dropped. * * Returns: The newly allocated and instantiated object. @@ -641,7 +645,7 @@ Object *object_new(const char *typename); * @...: list of property names and values * * This function will initialize a new object using heap allocated memory. - * The returned object has a reference count of 1, and will be freed when + * The returned object has a reference count of 1, and will be finalized when * the last reference is dropped. * * The @id parameter will be used when registering the object as a @@ -1120,8 +1124,8 @@ GSList *object_class_get_list_sorted(const char *implements_type, * object_ref: * @obj: the object * - * Increase the reference count of a object. A object cannot be freed as long - * as a reference remains. + * Increase the reference count of a object. A object cannot be finalized as + * long as a reference remains. * Returns: @obj */ Object *object_ref(void *obj); @@ -1130,11 +1134,30 @@ Object *object_ref(void *obj); * object_unref: * @obj: the object * - * Decrease the reference count of a object. A object cannot be freed as long - * as a reference remains. + * Decrease the reference count of a object. A object cannot be finalized as + * long as a reference remains. */ void object_unref(void *obj); +/** + * object_storage_ref: + * @obj: the object + * + * Increase the reference count of a object's storage. A object cannot be + * freed as long as its storage is referenced. + * Returns: the object that provides the storage + */ +Object *object_storage_ref(void *obj); + +/** + * object_storage_unref: + * @obj: the object + * + * Decrease the reference count of a object's storage. A object cannot be + * freed as long as its storage is referenced. + */ +void object_storage_unref(void *obj); + /** * object_property_try_add: * @obj: the object to add a property to diff --git a/qom/object.c b/qom/object.c index 761eff1337e6..0a2c7ce9710d 100644 --- a/qom/object.c +++ b/qom/object.c @@ -487,7 +487,8 @@ static void object_class_property_init_all(Object *obj) } } -static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type) +static void object_initialize_with_type(Object *obj, size_t size, + TypeImpl *type, Object *storage) { type_initialize(type); @@ -496,6 +497,10 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type g_assert(size >= type->instance_size); memset(obj, 0, type->instance_size); + if (storage) { + obj->storage_ref = UINT32_MAX; + obj->storage = object_storage_ref(storage); + } obj->class = type->class; object_class_property_init_all(obj); obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal, @@ -508,7 +513,7 @@ void object_initialize(void *data, size_t size, const char *typename) { TypeImpl *type = type_get_or_load_by_name(typename, &error_fatal); - object_initialize_with_type(data, size, type); + object_initialize_with_type(data, size, type, NULL); } bool object_initialize_child_with_props(Object *parentobj, @@ -531,14 +536,15 @@ bool object_initialize_child_with_props(Object *parentobj, bool object_initialize_child_with_propsv(Object *parentobj, const char *propname, void *childobj, size_t size, - const char *type, + const char *typename, Error **errp, va_list vargs) { + TypeImpl *type = type_get_or_load_by_name(typename, &error_fatal); bool ok = false; Object *obj; UserCreatable *uc; - object_initialize(childobj, size, type); + object_initialize_with_type(childobj, size, type, parentobj); obj = OBJECT(childobj); if (!object_set_propv(obj, vargs, errp)) { @@ -667,9 +673,7 @@ static void object_finalize(void *data) object_deinit(obj, ti); g_assert(obj->parent == NULL); - if (obj->free) { - obj->free(obj); - } + object_storage_unref(obj); } /* Find the minimum alignment guaranteed by the system malloc. */ @@ -708,7 +712,7 @@ static Object *object_new_with_type(Type type) obj_free = qemu_vfree; } - object_initialize_with_type(obj, size, type); + object_initialize_with_type(obj, size, type, NULL); obj->free = obj_free; trace_object_new(obj, obj->class->type->name); @@ -1334,6 +1338,46 @@ void object_unref(void *objptr) } } +Object *object_storage_ref(void *objptr) +{ + Object *obj = OBJECT(objptr); + uint32_t ref; + + if (!obj) { + return NULL; + } + + while (qatomic_read(&obj->storage_ref) == UINT32_MAX) { + obj = obj->storage; + } + + ref = qatomic_fetch_inc(&obj->storage_ref); + /* Assert waaay before the integer overflows */ + g_assert(ref < INT_MAX); + return obj; +} + +void object_storage_unref(void *objptr) +{ + Object *obj = OBJECT(objptr); + uint32_t ref; + + if (!obj) { + return; + } + + while (qatomic_read(&obj->storage_ref) == UINT32_MAX) { + obj = obj->storage; + } + + ref = qatomic_fetch_dec(&obj->storage_ref); + g_assert(ref < INT_MAX); + + if (ref == 0 && obj->free) { + obj->free(obj); + } +} + ObjectProperty * object_property_try_add(Object *obj, const char *name, const char *type, ObjectPropertyAccessor *get, diff --git a/rust/qom/src/qom.rs b/rust/qom/src/qom.rs index cc00ddcfc988..34f7fff64083 100644 --- a/rust/qom/src/qom.rs +++ b/rust/qom/src/qom.rs @@ -809,8 +809,8 @@ impl<T: ObjectType> Owned<T> { /// # Safety /// /// The caller must indeed own a reference to the QOM object. - /// The object must not be embedded in another unless the outer - /// object is guaranteed to have a longer lifetime. + /// The object's implementation must guarantee functionality as long as it + /// is referenced. /// /// A raw pointer obtained via [`Owned::into_raw()`] can always be passed /// back to `from_raw()` (assuming the original `Owned` was valid!), @@ -836,8 +836,8 @@ pub fn into_raw(src: Owned<T>) -> *mut T { /// /// # Safety /// - /// The object must not be embedded in another, unless the outer - /// object is guaranteed to have a longer lifetime. + /// The object's implementation must guarantee functionality as long as it + /// is referenced. pub unsafe fn from(obj: &T) -> Self { unsafe { object_ref(obj.as_object_mut_ptr().cast::<c_void>()); -- 2.54.0 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 4:11 ` [PATCH 2/2] qom: Manage references to embedded child objects Akihiko Odaki @ 2026-06-15 8:12 ` Daniel P. Berrangé 2026-06-15 8:31 ` Peter Maydell 2026-06-15 13:29 ` Mark Cave-Ayland 2026-06-15 10:59 ` Daniel P. Berrangé 1 sibling, 2 replies; 17+ messages in thread From: Daniel P. Berrangé @ 2026-06-15 8:12 UTC (permalink / raw) To: Akihiko Odaki Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, Jun 15, 2026 at 01:11:07PM +0900, Akihiko Odaki wrote: > Problem > ======= > > The Rust wrapper for object_ref() is marked unsafe and has: > > The object must not be embedded in another unless the outer > > object is guaranteed to have a longer lifetime. > > In other words, object_ref() does not work for embedded objects and > does not keep embedded objects alive. MemoryRegion has its own > memory_region_ref() helper to call object_ref() on its owner for this > reason. > > However, this is insufficient to avoid calling object_ref() for all > embedded objects. For example, consider an embedded Device that has a > MemoryRegion. When referencing a MemoryRegion for guest memory access, > QEMU automatically references the owning Device to keep the MemoryRegion > alive. However, that reference is ineffective if the Device itself is > embedded, because object_ref() does not keep the containing storage > alive. Do we know how many examples we have of embedding objects inside another ? I would much prefer if we forbid the embedding of objects. It is horrible design practice to have some QOM objects which can be freed via reference count and some which cannot. With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :| ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 8:12 ` Daniel P. Berrangé @ 2026-06-15 8:31 ` Peter Maydell 2026-06-15 8:34 ` Daniel P. Berrangé 2026-06-15 13:29 ` Mark Cave-Ayland 1 sibling, 1 reply; 17+ messages in thread From: Peter Maydell @ 2026-06-15 8:31 UTC (permalink / raw) To: Daniel P. Berrangé Cc: Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé <berrange@redhat.com> wrote: > Do we know how many examples we have of embedding objects inside > another ? It is an extremely common pattern for device and SoC model implementations; we've been recommending it for years. > I would much prefer if we forbid the embedding of objects. It is > horrible design practice to have some QOM objects which can be > freed via reference count and some which cannot. That would be a very large amount of code to rewrite to the new paradigm. I don't object inherently ("you have pointers to your child objects" works better when they might be implemented in Rust and might play better with being able to create machines and wire them up on the command line); I'm just noting how much work it would be if you wanted to make embedding forbidden. thanks -- PMM ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 8:31 ` Peter Maydell @ 2026-06-15 8:34 ` Daniel P. Berrangé 2026-06-15 9:28 ` Peter Maydell 0 siblings, 1 reply; 17+ messages in thread From: Daniel P. Berrangé @ 2026-06-15 8:34 UTC (permalink / raw) To: Peter Maydell Cc: Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, Jun 15, 2026 at 09:31:36AM +0100, Peter Maydell wrote: > On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé <berrange@redhat.com> wrote: > > Do we know how many examples we have of embedding objects inside > > another ? > > It is an extremely common pattern for device and SoC model > implementations; we've been recommending it for years. > > > I would much prefer if we forbid the embedding of objects. It is > > horrible design practice to have some QOM objects which can be > > freed via reference count and some which cannot. > > That would be a very large amount of code to rewrite to the > new paradigm. I don't object inherently ("you have pointers to > your child objects" works better when they might be implemented > in Rust and might play better with being able to create > machines and wire them up on the command line); I'm just > noting how much work it would be if you wanted to make > embedding forbidden. Would it be a more tractable problem to "fix" object structure only incrementally as they gain a need to be managed/reference from Rust code, or does the Rust usage already implicitly extend too broadly With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :| ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 8:34 ` Daniel P. Berrangé @ 2026-06-15 9:28 ` Peter Maydell 2026-06-15 13:42 ` Mark Cave-Ayland 0 siblings, 1 reply; 17+ messages in thread From: Peter Maydell @ 2026-06-15 9:28 UTC (permalink / raw) To: Daniel P. Berrangé Cc: Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, 15 Jun 2026 at 09:35, Daniel P. Berrangé <berrange@redhat.com> wrote: > > On Mon, Jun 15, 2026 at 09:31:36AM +0100, Peter Maydell wrote: > > On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé <berrange@redhat.com> wrote: > > > Do we know how many examples we have of embedding objects inside > > > another ? > > > > It is an extremely common pattern for device and SoC model > > implementations; we've been recommending it for years. > > > > > I would much prefer if we forbid the embedding of objects. It is > > > horrible design practice to have some QOM objects which can be > > > freed via reference count and some which cannot. > > > > That would be a very large amount of code to rewrite to the > > new paradigm. I don't object inherently ("you have pointers to > > your child objects" works better when they might be implemented > > in Rust and might play better with being able to create > > machines and wire them up on the command line); I'm just > > noting how much work it would be if you wanted to make > > embedding forbidden. > > Would it be a more tractable problem to "fix" object structure only > incrementally as they gain a need to be managed/reference from > Rust code, or does the Rust usage already implicitly extend too > broadly At the moment we have exactly 2 Rust devices. You can see the workaround we ended up with for the PL011 in commits 5b87a07e768 and cc3d262aa93a4, where we pad out the C device struct and assert on the Rust side that its version isn't any bigger. For that particular problem we could say "you need to refactor all users of device X to not embed it before creating the Rust version". There is I think only one embedded use of the PL011 (I was actually expecting more). This does have the awkward effect that anybody wanting to do a "rewrite device in rust" is now forced into "do a big refactor of some old C code they don't care about". We would also need to actually document and provide some good examples of "this is how we think we should be writing device models that have child objects now". But embedded MemoryRegions are enormously common, and devices that embed other devices that embed MemoryRegions are also pretty common. So I don't know how much of a gradual approach you could take for the issue descibed in this patch. thanks -- PMM ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 9:28 ` Peter Maydell @ 2026-06-15 13:42 ` Mark Cave-Ayland 2026-06-15 13:55 ` Akihiko Odaki ` (2 more replies) 0 siblings, 3 replies; 17+ messages in thread From: Mark Cave-Ayland @ 2026-06-15 13:42 UTC (permalink / raw) To: Peter Maydell, Daniel P. Berrangé Cc: Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On 15/06/2026 10:28, Peter Maydell wrote: > On Mon, 15 Jun 2026 at 09:35, Daniel P. Berrangé <berrange@redhat.com> wrote: >> >> On Mon, Jun 15, 2026 at 09:31:36AM +0100, Peter Maydell wrote: >>> On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé <berrange@redhat.com> wrote: >>>> Do we know how many examples we have of embedding objects inside >>>> another ? >>> >>> It is an extremely common pattern for device and SoC model >>> implementations; we've been recommending it for years. >>> >>>> I would much prefer if we forbid the embedding of objects. It is >>>> horrible design practice to have some QOM objects which can be >>>> freed via reference count and some which cannot. >>> >>> That would be a very large amount of code to rewrite to the >>> new paradigm. I don't object inherently ("you have pointers to >>> your child objects" works better when they might be implemented >>> in Rust and might play better with being able to create >>> machines and wire them up on the command line); I'm just >>> noting how much work it would be if you wanted to make >>> embedding forbidden. >> >> Would it be a more tractable problem to "fix" object structure only >> incrementally as they gain a need to be managed/reference from >> Rust code, or does the Rust usage already implicitly extend too >> broadly > > At the moment we have exactly 2 Rust devices. You can see the > workaround we ended up with for the PL011 in commits 5b87a07e768 > and cc3d262aa93a4, where we pad out the C device struct and > assert on the Rust side that its version isn't any bigger. > > For that particular problem we could say "you need to refactor > all users of device X to not embed it before creating the Rust > version". There is I think only one embedded use of the PL011 > (I was actually expecting more). This does have the awkward > effect that anybody wanting to do a "rewrite device in rust" > is now forced into "do a big refactor of some old C code they > don't care about". We would also need to actually document and > provide some good examples of "this is how we think we should > be writing device models that have child objects now". I think given the legacy of the QEMU codebase then this will always be an issue, but we've solved this in the past with the introduction of MemoryRegions so there's no reason we couldn't do it again. This is a similar problem to the use of non-class object properties I was discussing in the thread with Daniel: it seems the general consensus is that they shouldn't exist, but this hasn't been formalised anywhere. If we can formalise the decision not to use embedded QOM objects, then we can start by ensuring that new submissions don't use them and go from there. Question: who currently should make these decisions? Is it restricted to the QOM maintainers? > But embedded MemoryRegions are enormously common, and devices > that embed other devices that embed MemoryRegions are also > pretty common. So I don't know how much of a gradual approach > you could take for the issue descibed in this patch. Zoltan did post a patch that allowed for adding externally referenced MemoryRegions a while back: my reason for not supporting the approach was because it added yet another way of adding MemoryRegions to work around a particular legacy use case. However if there is agreement that this is the way we should do things, I suspect that the series will have solved most of the problems in this area. ATB, Mark. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 13:42 ` Mark Cave-Ayland @ 2026-06-15 13:55 ` Akihiko Odaki 2026-06-15 14:01 ` Daniel P. Berrangé 2026-06-15 20:09 ` BALATON Zoltan 2 siblings, 0 replies; 17+ messages in thread From: Akihiko Odaki @ 2026-06-15 13:55 UTC (permalink / raw) To: Mark Cave-Ayland, Peter Maydell, Daniel P. Berrangé Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On 2026/06/15 22:42, Mark Cave-Ayland wrote: > On 15/06/2026 10:28, Peter Maydell wrote: > >> On Mon, 15 Jun 2026 at 09:35, Daniel P. Berrangé <berrange@redhat.com> >> wrote: >>> >>> On Mon, Jun 15, 2026 at 09:31:36AM +0100, Peter Maydell wrote: >>>> On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé >>>> <berrange@redhat.com> wrote: >>>>> Do we know how many examples we have of embedding objects inside >>>>> another ? >>>> >>>> It is an extremely common pattern for device and SoC model >>>> implementations; we've been recommending it for years. >>>> >>>>> I would much prefer if we forbid the embedding of objects. It is >>>>> horrible design practice to have some QOM objects which can be >>>>> freed via reference count and some which cannot. >>>> >>>> That would be a very large amount of code to rewrite to the >>>> new paradigm. I don't object inherently ("you have pointers to >>>> your child objects" works better when they might be implemented >>>> in Rust and might play better with being able to create >>>> machines and wire them up on the command line); I'm just >>>> noting how much work it would be if you wanted to make >>>> embedding forbidden. >>> >>> Would it be a more tractable problem to "fix" object structure only >>> incrementally as they gain a need to be managed/reference from >>> Rust code, or does the Rust usage already implicitly extend too >>> broadly >> >> At the moment we have exactly 2 Rust devices. You can see the >> workaround we ended up with for the PL011 in commits 5b87a07e768 >> and cc3d262aa93a4, where we pad out the C device struct and >> assert on the Rust side that its version isn't any bigger. >> >> For that particular problem we could say "you need to refactor >> all users of device X to not embed it before creating the Rust >> version". There is I think only one embedded use of the PL011 >> (I was actually expecting more). This does have the awkward >> effect that anybody wanting to do a "rewrite device in rust" >> is now forced into "do a big refactor of some old C code they >> don't care about". We would also need to actually document and >> provide some good examples of "this is how we think we should >> be writing device models that have child objects now". > > I think given the legacy of the QEMU codebase then this will always be > an issue, but we've solved this in the past with the introduction of > MemoryRegions so there's no reason we couldn't do it again. > > This is a similar problem to the use of non-class object properties I > was discussing in the thread with Daniel: it seems the general consensus > is that they shouldn't exist, but this hasn't been formalised anywhere. > If we can formalise the decision not to use embedded QOM objects, then > we can start by ensuring that new submissions don't use them and go from > there. > > Question: who currently should make these decisions? Is it restricted to > the QOM maintainers? > >> But embedded MemoryRegions are enormously common, and devices >> that embed other devices that embed MemoryRegions are also >> pretty common. So I don't know how much of a gradual approach >> you could take for the issue descibed in this patch. > > Zoltan did post a patch that allowed for adding externally referenced > MemoryRegions a while back: my reason for not supporting the approach > was because it added yet another way of adding MemoryRegions to work > around a particular legacy use case. However if there is agreement that > this is the way we should do things, I suspect that the series will have > solved most of the problems in this area. Personally, I do not have a strong preference between supporting references to embedded objects and deprecating object embedding entirely, so this patch takes the smaller step needed to address the immediate issue. I would not object to deprecating embedding if that is the direction we want to take. Regards, Akihiko Odaki ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 13:42 ` Mark Cave-Ayland 2026-06-15 13:55 ` Akihiko Odaki @ 2026-06-15 14:01 ` Daniel P. Berrangé 2026-06-15 20:09 ` BALATON Zoltan 2 siblings, 0 replies; 17+ messages in thread From: Daniel P. Berrangé @ 2026-06-15 14:01 UTC (permalink / raw) To: Mark Cave-Ayland Cc: Peter Maydell, Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, Jun 15, 2026 at 02:42:10PM +0100, Mark Cave-Ayland wrote: > On 15/06/2026 10:28, Peter Maydell wrote: > > > On Mon, 15 Jun 2026 at 09:35, Daniel P. Berrangé <berrange@redhat.com> wrote: > > > > > > On Mon, Jun 15, 2026 at 09:31:36AM +0100, Peter Maydell wrote: > > > > On Mon, 15 Jun 2026 at 09:13, Daniel P. Berrangé <berrange@redhat.com> wrote: > > > > > Do we know how many examples we have of embedding objects inside > > > > > another ? > > > > > > > > It is an extremely common pattern for device and SoC model > > > > implementations; we've been recommending it for years. > > > > > > > > > I would much prefer if we forbid the embedding of objects. It is > > > > > horrible design practice to have some QOM objects which can be > > > > > freed via reference count and some which cannot. > > > > > > > > That would be a very large amount of code to rewrite to the > > > > new paradigm. I don't object inherently ("you have pointers to > > > > your child objects" works better when they might be implemented > > > > in Rust and might play better with being able to create > > > > machines and wire them up on the command line); I'm just > > > > noting how much work it would be if you wanted to make > > > > embedding forbidden. > > > > > > Would it be a more tractable problem to "fix" object structure only > > > incrementally as they gain a need to be managed/reference from > > > Rust code, or does the Rust usage already implicitly extend too > > > broadly > > > > At the moment we have exactly 2 Rust devices. You can see the > > workaround we ended up with for the PL011 in commits 5b87a07e768 > > and cc3d262aa93a4, where we pad out the C device struct and > > assert on the Rust side that its version isn't any bigger. > > > > For that particular problem we could say "you need to refactor > > all users of device X to not embed it before creating the Rust > > version". There is I think only one embedded use of the PL011 > > (I was actually expecting more). This does have the awkward > > effect that anybody wanting to do a "rewrite device in rust" > > is now forced into "do a big refactor of some old C code they > > don't care about". We would also need to actually document and > > provide some good examples of "this is how we think we should > > be writing device models that have child objects now". > > I think given the legacy of the QEMU codebase then this will always be an > issue, but we've solved this in the past with the introduction of > MemoryRegions so there's no reason we couldn't do it again. > > This is a similar problem to the use of non-class object properties I was > discussing in the thread with Daniel: it seems the general consensus is that > they shouldn't exist, but this hasn't been formalised anywhere. If we can > formalise the decision not to use embedded QOM objects, then we can start by > ensuring that new submissions don't use them and go from there. > > Question: who currently should make these decisions? Is it restricted to the > QOM maintainers? Ultimately it would need a docs update on object.h to declare the object_initialize_child related methods to be deprecated which the QOM maintainers would have to queue. Since this impacts device maintainers in general though, IMHO we need broad consensus that it is a better approach & we're willing to convert existing code incrementally. We have ~150 devices using this for 824 children $ git grep -l object_initialize_child | wc -l 150 $ git grep object_initialize_child | wc -l 824 Where updates are desired, we'd be looking at tedious changes like thsi: diff --git a/hw/isa/piix.c b/hw/isa/piix.c index 31fa53e6a4..86757c56e5 100644 --- a/hw/isa/piix.c +++ b/hw/isa/piix.c @@ -355,16 +355,23 @@ static void pci_piix_realize(PCIDevice *dev, const char *uhci_type, /* USB */ if (d->has_usb) { - object_initialize_child(OBJECT(dev), "uhci", &d->uhci, uhci_type); - qdev_prop_set_int32(DEVICE(&d->uhci), "addr", dev->devfn + 2); - if (!qdev_realize(DEVICE(&d->uhci), BUS(pci_bus), errp)) { + d->uhci = UHCI(object_new_with_props( + uhci_type, OBJECT(d), "uhci", errp, NULL)); + if (!d->uhci) { + return; + } + qdev_prop_set_int32(DEVICE(d->uhci), "addr", dev->devfn + 2); + if (!qdev_realize(DEVICE(d->uhci), BUS(pci_bus), errp)) { return; } } diff --git a/hw/isa/piix.c b/hw/isa/piix.c index 31fa53e6a4..86757c56e5 100644 --- a/hw/isa/piix.c +++ b/hw/isa/piix.c @@ -355,16 +355,23 @@ static void pci_piix_realize(PCIDevice *dev, const char *uhci_type, /* USB */ if (d->has_usb) { - object_initialize_child(OBJECT(dev), "uhci", &d->uhci, uhci_type); - qdev_prop_set_int32(DEVICE(&d->uhci), "addr", dev->devfn + 2); - if (!qdev_realize(DEVICE(&d->uhci), BUS(pci_bus), errp)) { + d->uhci = UHCI(object_new_with_props( + uhci_type, OBJECT(d), "uhci", errp, NULL)); + if (!d->uhci) { + return; + } + qdev_prop_set_int32(DEVICE(d->uhci), "addr", dev->devfn + 2); + if (!qdev_realize(DEVICE(d->uhci), BUS(pci_bus), errp)) { return; } } Noting that the compiler won't complain if we forget to update any lines like - qdev_prop_set_int32(DEVICE(&d->uhci), "addr", dev->devfn + 2); + qdev_prop_set_int32(DEVICE(d->uhci), "addr", dev->devfn + 2); because the explicit DEVICE(..) cast is hiding the type error we would otherwise get from using "Device **" instead of "Device *". With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :| ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 13:42 ` Mark Cave-Ayland 2026-06-15 13:55 ` Akihiko Odaki 2026-06-15 14:01 ` Daniel P. Berrangé @ 2026-06-15 20:09 ` BALATON Zoltan 2 siblings, 0 replies; 17+ messages in thread From: BALATON Zoltan @ 2026-06-15 20:09 UTC (permalink / raw) To: Mark Cave-Ayland Cc: Peter Maydell, Daniel P. Berrangé, Akihiko Odaki, qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, 15 Jun 2026, Mark Cave-Ayland wrote: >> But embedded MemoryRegions are enormously common, and devices >> that embed other devices that embed MemoryRegions are also >> pretty common. So I don't know how much of a gradual approach >> you could take for the issue descibed in this patch. > > Zoltan did post a patch that allowed for adding externally referenced > MemoryRegions a while back: my reason for not supporting the approach was > because it added yet another way of adding MemoryRegions to work around a > particular legacy use case. However if there is agreement that this is the > way we should do things, I suspect that the series will have solved most of > the problems in this area. That series was blocked by Paolo saying no: https://lists.nongnu.org/archive/html/qemu-devel/2026-05/msg06665.html without ever explaining why it should not be allowed to create ref counted memory regions. even though it's documented and implemented that way already, but the current memory_region_init() functions don't allow using that and forces every memory region to either be embedded or leaked. I still think that using QOM to manage memory region life time with ref counts is useful as it also makes device models simpler and my series allows incremental conversion as it only adds the functions to create QOM managed memory regions leaving the exising embedded regions untouched so they could be converted one by one when needed. If there's more interest in this now I can update and repost it again or I'd still like to see an explanation why it is not wanted. Regards, BALATON Zoltan ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 8:12 ` Daniel P. Berrangé 2026-06-15 8:31 ` Peter Maydell @ 2026-06-15 13:29 ` Mark Cave-Ayland 1 sibling, 0 replies; 17+ messages in thread From: Mark Cave-Ayland @ 2026-06-15 13:29 UTC (permalink / raw) To: Daniel P. Berrangé, Akihiko Odaki Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On 15/06/2026 09:12, Daniel P. Berrangé wrote: > On Mon, Jun 15, 2026 at 01:11:07PM +0900, Akihiko Odaki wrote: >> Problem >> ======= >> >> The Rust wrapper for object_ref() is marked unsafe and has: >>> The object must not be embedded in another unless the outer >>> object is guaranteed to have a longer lifetime. >> >> In other words, object_ref() does not work for embedded objects and >> does not keep embedded objects alive. MemoryRegion has its own >> memory_region_ref() helper to call object_ref() on its owner for this >> reason. >> >> However, this is insufficient to avoid calling object_ref() for all >> embedded objects. For example, consider an embedded Device that has a >> MemoryRegion. When referencing a MemoryRegion for guest memory access, >> QEMU automatically references the owning Device to keep the MemoryRegion >> alive. However, that reference is ineffective if the Device itself is >> embedded, because object_ref() does not keep the containing storage >> alive. > > Do we know how many examples we have of embedding objects inside > another ? > > I would much prefer if we forbid the embedding of objects. It is > horrible design practice to have some QOM objects which can be > freed via reference count and some which cannot. This is something I've been discussing with a few other people for some time now: shouldn't we deprecate object_initialize_child() and use references for everything instead? Not only would this help solve a lot of lifecycle problems, but it would help reviewers because at the moment we have too many different ways of doing the same thing. ATB, Mark. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 4:11 ` [PATCH 2/2] qom: Manage references to embedded child objects Akihiko Odaki 2026-06-15 8:12 ` Daniel P. Berrangé @ 2026-06-15 10:59 ` Daniel P. Berrangé 2026-06-15 12:02 ` Akihiko Odaki 1 sibling, 1 reply; 17+ messages in thread From: Daniel P. Berrangé @ 2026-06-15 10:59 UTC (permalink / raw) To: Akihiko Odaki Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On Mon, Jun 15, 2026 at 01:11:07PM +0900, Akihiko Odaki wrote: > One concrete case is qemu-xhci: XHCIPciState embeds XHCIState as a > child object, and the embedded XHCIState owns the MemoryRegion used for > PCI BAR 0. When memory core references that region, memory_region_ref() > references the embedded XHCIState owner, but that does not keep the > containing XHCIPciState storage alive across runtime unplug. Taking this example and looking at the patch, Immediately after XHCIPciState is allocated, we'll have the following XHCIPciState QOM state: free == object_free storage == <undefined> ref == 1 storage_ref == 1 <up-ref from XHCIState> Meanwhile XHCIState is embedded and so after construction it has free == <undefined> storage == ptr<XHCIPciState> ref == 1 storage_ref == UINT32_MAX The simple scenario, no 3rd party refs on XHCIState, then we have a flow that looks like this: object_unref(XHCIPciState) => ref == 0 => object_finalize(XHCIPciState) => object_unref(XHCIState) => ref == 0 => object_storage_unref(XHCIState) => obj = obj.storage => XHCIPciState.storage_ref-- => XHCIPciState.storage_ref == 0 => object_free(XHCIPciState) => Remainder of ...object_finalize(XHCIPciState) is using free'd mem now... This doesn't seem to work AFAICS ? Now lets consider something took a reference on XHCIState first. object_ref(XHCIState) object_unref(XHCIPciState) => ref == 0 => object_finalize(XHCIPciState) => object_unref(XHCIState) => ref == 1 => object_storage_unref(XHCIPciState) => storage_ref-- => storage_ref == 0 => object_free(XHCIPciState) object_unref(XHCIState) => ref == 0 => object_storage_unref(XHCIPciState) => storage_ref == -1 Again this doesn't seem to actually work as billed ? For the sake of argument though, lets say this storage_ref was handled correctly such that we delay free'ing the XHCIPciState memory until *both* XHCIPciState and XHCIState have hit ref == 0, and both their finalize methods are completed. I'm still not convinced this would actually be safe. This patch is delaying the free'ing of the memory that XHCIState is embedded in, but is the embedded XHCIState still functional enough when its parent and all sibling objects will have been finalized already ? That is not obviously ok to me. The "XHCIPciState" will have been finalized, but all its fields are mostly pointing to data that has been deinitialized. Can we really assume there are no dependencies between the embedded objects ? Conceptually it feels like finalization of XHCIPciState ought to be an all-or-nothing affair, rather than finalizing XHCIPciState and some of its embedded children but arbtirarily keeping other children alive. I'm not sure how to arrange that though - its essentially a need for a circular reference, but then how/when do you break it. > diff --git a/include/qom/object.h b/include/qom/object.h > index c4dcf65dcf7f..b5d656e3758c 100644 > --- a/include/qom/object.h > +++ b/include/qom/object.h > @@ -155,8 +155,12 @@ struct Object > { > /* private: */ > ObjectClass *class; > - ObjectFree *free; > + union { > + ObjectFree *free; > + Object *storage; > + }; > GHashTable *properties; > + uint32_t storage_ref; > uint32_t ref; > Object *parent; > }; > @@ -613,7 +617,7 @@ struct InterfaceClass > * @klass: The class to instantiate. > * > * This function will initialize a new object using heap allocated memory. > - * The returned object has a reference count of 1, and will be freed when > + * The returned object has a reference count of 1, and will be finalized when > * the last reference is dropped. > * > * Returns: The newly allocated and instantiated object. > @@ -625,7 +629,7 @@ Object *object_new_with_class(ObjectClass *klass); > * @typename: The name of the type of the object to instantiate. > * > * This function will initialize a new object using heap allocated memory. > - * The returned object has a reference count of 1, and will be freed when > + * The returned object has a reference count of 1, and will be finalized when > * the last reference is dropped. > * > * Returns: The newly allocated and instantiated object. > @@ -641,7 +645,7 @@ Object *object_new(const char *typename); > * @...: list of property names and values > * > * This function will initialize a new object using heap allocated memory. > - * The returned object has a reference count of 1, and will be freed when > + * The returned object has a reference count of 1, and will be finalized when > * the last reference is dropped. > * > * The @id parameter will be used when registering the object as a > @@ -1120,8 +1124,8 @@ GSList *object_class_get_list_sorted(const char *implements_type, > * object_ref: > * @obj: the object > * > - * Increase the reference count of a object. A object cannot be freed as long > - * as a reference remains. > + * Increase the reference count of a object. A object cannot be finalized as > + * long as a reference remains. > * Returns: @obj > */ > Object *object_ref(void *obj); > @@ -1130,11 +1134,30 @@ Object *object_ref(void *obj); > * object_unref: > * @obj: the object > * > - * Decrease the reference count of a object. A object cannot be freed as long > - * as a reference remains. > + * Decrease the reference count of a object. A object cannot be finalized as > + * long as a reference remains. > */ > void object_unref(void *obj); > > +/** > + * object_storage_ref: > + * @obj: the object > + * > + * Increase the reference count of a object's storage. A object cannot be > + * freed as long as its storage is referenced. > + * Returns: the object that provides the storage > + */ > +Object *object_storage_ref(void *obj); > + > +/** > + * object_storage_unref: > + * @obj: the object > + * > + * Decrease the reference count of a object's storage. A object cannot be > + * freed as long as its storage is referenced. > + */ > +void object_storage_unref(void *obj); > + > /** > * object_property_try_add: > * @obj: the object to add a property to > diff --git a/qom/object.c b/qom/object.c > index 761eff1337e6..0a2c7ce9710d 100644 > --- a/qom/object.c > +++ b/qom/object.c > @@ -487,7 +487,8 @@ static void object_class_property_init_all(Object *obj) > } > } > > -static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type) > +static void object_initialize_with_type(Object *obj, size_t size, > + TypeImpl *type, Object *storage) > { > type_initialize(type); > > @@ -496,6 +497,10 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type > g_assert(size >= type->instance_size); > > memset(obj, 0, type->instance_size); > + if (storage) { > + obj->storage_ref = UINT32_MAX; > + obj->storage = object_storage_ref(storage); > + } > obj->class = type->class; > object_class_property_init_all(obj); > obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal, > @@ -508,7 +513,7 @@ void object_initialize(void *data, size_t size, const char *typename) > { > TypeImpl *type = type_get_or_load_by_name(typename, &error_fatal); > > - object_initialize_with_type(data, size, type); > + object_initialize_with_type(data, size, type, NULL); > } > > bool object_initialize_child_with_props(Object *parentobj, > @@ -531,14 +536,15 @@ bool object_initialize_child_with_props(Object *parentobj, > bool object_initialize_child_with_propsv(Object *parentobj, > const char *propname, > void *childobj, size_t size, > - const char *type, > + const char *typename, > Error **errp, va_list vargs) > { > + TypeImpl *type = type_get_or_load_by_name(typename, &error_fatal); > bool ok = false; > Object *obj; > UserCreatable *uc; > > - object_initialize(childobj, size, type); > + object_initialize_with_type(childobj, size, type, parentobj); > obj = OBJECT(childobj); > > if (!object_set_propv(obj, vargs, errp)) { > @@ -667,9 +673,7 @@ static void object_finalize(void *data) > object_deinit(obj, ti); > > g_assert(obj->parent == NULL); > - if (obj->free) { > - obj->free(obj); > - } > + object_storage_unref(obj); > } > > /* Find the minimum alignment guaranteed by the system malloc. */ > @@ -708,7 +712,7 @@ static Object *object_new_with_type(Type type) > obj_free = qemu_vfree; > } > > - object_initialize_with_type(obj, size, type); > + object_initialize_with_type(obj, size, type, NULL); > obj->free = obj_free; > > trace_object_new(obj, obj->class->type->name); > @@ -1334,6 +1338,46 @@ void object_unref(void *objptr) > } > } > > +Object *object_storage_ref(void *objptr) > +{ > + Object *obj = OBJECT(objptr); > + uint32_t ref; > + > + if (!obj) { > + return NULL; > + } > + > + while (qatomic_read(&obj->storage_ref) == UINT32_MAX) { > + obj = obj->storage; > + } > + > + ref = qatomic_fetch_inc(&obj->storage_ref); > + /* Assert waaay before the integer overflows */ > + g_assert(ref < INT_MAX); > + return obj; > +} > + > +void object_storage_unref(void *objptr) > +{ > + Object *obj = OBJECT(objptr); > + uint32_t ref; > + > + if (!obj) { > + return; > + } > + > + while (qatomic_read(&obj->storage_ref) == UINT32_MAX) { > + obj = obj->storage; > + } > + > + ref = qatomic_fetch_dec(&obj->storage_ref); > + g_assert(ref < INT_MAX); > + > + if (ref == 0 && obj->free) { > + obj->free(obj); > + } > +} > + > ObjectProperty * > object_property_try_add(Object *obj, const char *name, const char *type, > ObjectPropertyAccessor *get, > diff --git a/rust/qom/src/qom.rs b/rust/qom/src/qom.rs > index cc00ddcfc988..34f7fff64083 100644 > --- a/rust/qom/src/qom.rs > +++ b/rust/qom/src/qom.rs > @@ -809,8 +809,8 @@ impl<T: ObjectType> Owned<T> { > /// # Safety > /// > /// The caller must indeed own a reference to the QOM object. > - /// The object must not be embedded in another unless the outer > - /// object is guaranteed to have a longer lifetime. > + /// The object's implementation must guarantee functionality as long as it > + /// is referenced. > /// > /// A raw pointer obtained via [`Owned::into_raw()`] can always be passed > /// back to `from_raw()` (assuming the original `Owned` was valid!), > @@ -836,8 +836,8 @@ pub fn into_raw(src: Owned<T>) -> *mut T { > /// > /// # Safety > /// > - /// The object must not be embedded in another, unless the outer > - /// object is guaranteed to have a longer lifetime. > + /// The object's implementation must guarantee functionality as long as it > + /// is referenced. > pub unsafe fn from(obj: &T) -> Self { > unsafe { > object_ref(obj.as_object_mut_ptr().cast::<c_void>()); > > -- > 2.54.0 > With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :| ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/2] qom: Manage references to embedded child objects 2026-06-15 10:59 ` Daniel P. Berrangé @ 2026-06-15 12:02 ` Akihiko Odaki 0 siblings, 0 replies; 17+ messages in thread From: Akihiko Odaki @ 2026-06-15 12:02 UTC (permalink / raw) To: Daniel P. Berrangé Cc: qemu-devel, Paolo Bonzini, Manos Pitsidianakis, qemu-rust, Peter Xu, Philippe Mathieu-Daudé, Alberto Garcia, Kevin Wolf, Hanna Reitz, qemu-block On 2026/06/15 19:59, Daniel P. Berrangé wrote: > On Mon, Jun 15, 2026 at 01:11:07PM +0900, Akihiko Odaki wrote: >> One concrete case is qemu-xhci: XHCIPciState embeds XHCIState as a >> child object, and the embedded XHCIState owns the MemoryRegion used for >> PCI BAR 0. When memory core references that region, memory_region_ref() >> references the embedded XHCIState owner, but that does not keep the >> containing XHCIPciState storage alive across runtime unplug. > > Taking this example and looking at the patch, > > Immediately after XHCIPciState is allocated, we'll have the > following XHCIPciState QOM state: > > free == object_free > storage == <undefined> > ref == 1 > storage_ref == 1 <up-ref from XHCIState> It should be: ref == 0 storage_ref == 1 Please see "[PATCH 1/2] qom: Reject temporary object resurrection" for the context. `Object::ref` stores the logical reference count minus one, so `ref == 0` means one reference remains. `storage_ref` follows the same convention for non-embedded storage. Here, `storage_ref == 1` means two storage references: the storage's own lifetime reference, plus the reference held via the embedded XHCIState. > > Meanwhile XHCIState is embedded and so after construction it > has > > free == <undefined> > storage == ptr<XHCIPciState> > ref == 1 > storage_ref == UINT32_MAX `ref == 0` here too. > > > The simple scenario, no 3rd party refs on XHCIState, then we > have a flow that looks like this: > > object_unref(XHCIPciState) > => ref == 0 This makes `ref == UINT32_MAX`; that marks the object as being finalized. > => object_finalize(XHCIPciState) > => object_unref(XHCIState) > => ref == 0 This also makes `XHCIState.ref == UINT32_MAX`. > => object_storage_unref(XHCIState) > => obj = obj.storage > => XHCIPciState.storage_ref-- > => XHCIPciState.storage_ref == 0 Yes. At this point there is still one storage reference left, represented as `storage_ref == 0`: the storage's own lifetime reference, held by the XHCIPciState finalization path itself. > => object_free(XHCIPciState) `obj->free()` does not happen when `XHCIPciState.storage_ref == 0`. > => Remainder of ...object_finalize(XHCIPciState) is > using free'd mem now... The free happens only after `object_finalize(XHCIPciState)` reaches its own final `object_storage_unref(XHCIPciState)`. That decrements `XHCIPciState.storage_ref` from `0` to `UINT32_MAX`, and only then calls `obj->free()`. > > This doesn't seem to work AFAICS ? > > Now lets consider something took a reference on XHCIState > first. > > object_ref(XHCIState) > object_unref(XHCIPciState) > => ref == 0 > => object_finalize(XHCIPciState) > => object_unref(XHCIState) > => ref == 1 > => object_storage_unref(XHCIPciState) > => storage_ref-- > => storage_ref == 0 > => object_free(XHCIPciState) > > object_unref(XHCIState) > => ref == 0 > => object_storage_unref(XHCIPciState) > => storage_ref == -1 > > Again this doesn't seem to actually work as billed ? With the minus-one representation, this becomes: object_ref(XHCIState) => XHCIState.ref goes from 0 to 1 object_unref(XHCIPciState) => XHCIPciState.ref goes from 0 to UINT32_MAX => object_finalize(XHCIPciState) => object_unref(XHCIState) => XHCIState.ref goes from 1 to 0 => XHCIState is not finalized yet => object_storage_unref(XHCIPciState) => XHCIPciState.storage_ref goes from 1 to 0 => no free yet object_unref(XHCIState) => XHCIState.ref goes from 0 to UINT32_MAX => object_finalize(XHCIState) => object_storage_unref(XHCIState) => follows XHCIState.storage to XHCIPciState => XHCIPciState.storage_ref goes from 0 to UINT32_MAX => object_free(XHCIPciState) So the containing storage is not freed until both finalizers have completed. > > > For the sake of argument though, lets say this storage_ref > was handled correctly such that we delay free'ing the > XHCIPciState memory until *both* XHCIPciState and > XHCIState have hit ref == 0, and both their finalize > methods are completed. > > I'm still not convinced this would actually be safe. > > This patch is delaying the free'ing of the memory that > XHCIState is embedded in, but is the embedded XHCIState > still functional enough when its parent and all sibling > objects will have been finalized already ? That is not > obviously ok to me. The "XHCIPciState" will have been > finalized, but all its fields are mostly pointing to > data that has been deinitialized. Can we really assume > there are no dependencies between the embedded objects ? That concern is valid. Keeping the containing storage allocated does not by itself guarantee that the embedded object remains fully functional after its parent has started finalization. That is also why the Rust wrapper for `object_ref()` remains unsafe. I think this is orthogonal to the storage lifetime problem this patch solves. The same functional-lifetime problem can exist even if XHCIState is not embedded and is allocated with `object_new()`, because it may still depend on state owned by the parent or by sibling objects. This patch prevents freeing the memory backing an externally referenced embedded object. It does not claim that every object implementation is safe to operate after the parent has begun unrealize/finalization. > > > Conceptually it feels like finalization of XHCIPciState > ought to be an all-or-nothing affair, rather than > finalizing XHCIPciState and some of its embedded children > but arbtirarily keeping other children alive. I'm not > sure how to arrange that though - its essentially a need > for a circular reference, but then how/when do you break > it. I agree that all-or-nothing finalization is attractive conceptually, but I do not think it gives enough semantics once objects can refer to each other. We still need to define an ordering: for example, if a parent and an embedded child refer to each other, either the child is finalized first and must not be accessed by the parent afterwards, or the parent is finalized first and the child must not access the parent afterwards. So the practical rule cannot just be "finalize the aggregate as one unit". We need explicit ordering and state transitions. In the case where the parent is finalized before an externally referenced child, the child may remain allocated, but it must treat the parent-side state as gone and avoid accessing it. In the MemoryRegion case described in the commit message, one possible higher-level fix is exactly this kind of ordering rule: once the device is being unrealized, accesses through the device's memory regions should be forbidden or should fail gracefully. The MemoryRegion object may remain referenced for a while, but late accesses should not rely on the parent device still being operational. Regards, Akihiko Odaki ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-15 20:10 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-15 4:11 [PATCH 0/2] qom: Tighten object lifetime handling Akihiko Odaki 2026-06-15 4:11 ` [PATCH 1/2] qom: Reject temporary object resurrection Akihiko Odaki 2026-06-15 8:14 ` Daniel P. Berrangé 2026-06-15 8:34 ` Kevin Wolf 2026-06-15 8:50 ` Akihiko Odaki 2026-06-15 4:11 ` [PATCH 2/2] qom: Manage references to embedded child objects Akihiko Odaki 2026-06-15 8:12 ` Daniel P. Berrangé 2026-06-15 8:31 ` Peter Maydell 2026-06-15 8:34 ` Daniel P. Berrangé 2026-06-15 9:28 ` Peter Maydell 2026-06-15 13:42 ` Mark Cave-Ayland 2026-06-15 13:55 ` Akihiko Odaki 2026-06-15 14:01 ` Daniel P. Berrangé 2026-06-15 20:09 ` BALATON Zoltan 2026-06-15 13:29 ` Mark Cave-Ayland 2026-06-15 10:59 ` Daniel P. Berrangé 2026-06-15 12:02 ` Akihiko Odaki
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.