diff --git a/include/qom/object.h b/include/qom/object.h
index 1634294e4f..a124cf897d 100644
--- a/include/qom/object.h
+++ b/include/qom/object.h
@@ -137,6 +137,8 @@ struct ObjectClass
     ObjectUnparent *unparent;
 
     GHashTable *properties;
+    /* instance properties locked.  See object_class_lock_properties() */
+    bool properties_locked;
 };
 
 /**
@@ -1867,6 +1869,21 @@ void object_property_set_description(Object *obj, const char *name,
 void object_class_property_set_description(ObjectClass *klass, const char *name,
                                            const char *description);
 
+/**
+ * object_class_lock_properties:
+ * @oc: the object class to have properties locked
+ *
+ * Prevent all subtypes of @oc from having writeable instance
+ * properties. If @oc is an interface type, this also affects all
+ * classes implementing the interface.
+ *
+ * This can be used by QOM types that have all QOM properties
+ * exposed to the external world (e.g. #TYPE_USER_CREATABLE) to
+ * ensure all user-writable properties are introspectable at the
+ * class level.
+ */
+void object_class_lock_properties(ObjectClass *oc);
+
 /**
  * object_child_foreach:
  * @obj: the object whose children will be navigated
diff --git a/qom/object.c b/qom/object.c
index bb32f5d3ad..73f27b8b7e 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -498,6 +498,27 @@ static void object_class_property_init_all(Object *obj)
     }
 }
 
+void object_class_lock_properties(ObjectClass *oc)
+{
+    oc->properties_locked = true;
+}
+
+static bool object_class_properties_locked(ObjectClass *oc)
+{
+    GSList *i = NULL;
+
+    if (oc->properties_locked) {
+        return true;
+    }
+    for (i = oc->interfaces; i; i = i->next) {
+        ObjectClass *ic = i->data;
+        if (ic->properties_locked) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type)
 {
     type_initialize(type);
@@ -1192,8 +1213,15 @@ object_property_try_add(Object *obj, const char *name, const char *type,
                         void *opaque, Error **errp)
 {
     ObjectProperty *prop;
+    ObjectClass *oc = object_get_class(obj);
     size_t name_len = strlen(name);
 
+    if (set && object_class_properties_locked(oc)) {
+        error_setg(errp, "writable instance property not allowed for type %s",
+                   object_class_get_name(oc));
+        return NULL;
+    }
+
     if (name_len >= 3 && !memcmp(name + name_len - 3, "[*]", 4)) {
         int i;
         ObjectProperty *ret;
diff --git a/tests/test-qdev-global-props.c b/tests/test-qdev-global-props.c
index c8862cac5f..590c916c4b 100644
--- a/tests/test-qdev-global-props.c
+++ b/tests/test-qdev-global-props.c
@@ -58,6 +58,9 @@ static void static_prop_class_init(ObjectClass *oc, void *data)
 
     dc->realize = NULL;
     device_class_set_props(dc, static_props);
+
+    /* test_proplist_lock() will check if property locking works */
+    object_class_lock_properties(oc);
 }
 
 static const TypeInfo static_prop_type = {
@@ -213,6 +216,69 @@ static const TypeInfo nondevice_type = {
     .parent = TYPE_OBJECT,
 };
 
+static void locked_interface_class_base_init(ObjectClass *klass, void *data)
+{
+    object_class_lock_properties(klass);
+}
+
+#define TYPE_LOCKED_INTERFACE "locked-interface"
+static const TypeInfo locked_interface_type = {
+    .name            = TYPE_LOCKED_INTERFACE,
+    .parent          = TYPE_INTERFACE,
+    .class_base_init = locked_interface_class_base_init,
+};
+
+#define TYPE_LOCKED_BY_INTERFACE "locked-by-interface"
+static const TypeInfo locked_by_interface_type = {
+    .name   = TYPE_LOCKED_BY_INTERFACE,
+    .parent = TYPE_OBJECT,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_LOCKED_INTERFACE },
+        { },
+    },
+};
+
+/* Make sure QOM property locking works as expected */
+static void test_proplist_lock(void)
+{
+    g_autoptr(Object) dynamic_obj = object_new(TYPE_DYNAMIC_PROPS);
+    g_autoptr(Object) static_obj = object_new(TYPE_STATIC_PROPS);
+    g_autoptr(Object) locked = object_new(TYPE_LOCKED_BY_INTERFACE);
+    Error *err = NULL;
+
+    /* read-only property: should always work */
+    object_property_try_add(dynamic_obj, "dynamic-prop-ro", "uint32",
+                            prop1_accessor, NULL,
+                            NULL, NULL, &error_abort);
+    object_property_try_add(static_obj, "dynamic-prop-ro", "uint32",
+                            prop1_accessor, NULL,
+                            NULL, NULL, &error_abort);
+    object_property_try_add(locked, "dynamic-prop-ro", "uint32",
+                            prop1_accessor, NULL,
+                            NULL, NULL, &error_abort);
+
+
+    /* read-write property: */
+
+    /* TYPE_DYNAMIC_PROPS is not locked */
+    object_property_try_add(dynamic_obj, "dynamic-prop-rw", "uint32",
+                            prop1_accessor, prop1_accessor,
+                            NULL, NULL, &error_abort);
+
+    /* TYPE_STATIC_PROPS is locked */
+    object_property_try_add(static_obj, "dynamic-prop-rw", "uint32",
+                            prop1_accessor, prop1_accessor,
+                            NULL, NULL, &err);
+    error_free_or_abort(&err);
+
+    /* TYPE_LOCKED_BY_INTERFACE is locked by interface type */
+    object_property_try_add(locked, "dynamic-prop-rw", "uint32",
+                            prop1_accessor, prop1_accessor,
+                            NULL, NULL, &err);
+    error_free_or_abort(&err);
+}
+
+
 /* Test setting of dynamic properties using global properties */
 static void test_dynamic_globalprop_subprocess(void)
 {
@@ -294,6 +360,10 @@ int main(int argc, char **argv)
     type_register_static(&hotplug_type);
     type_register_static(&nohotplug_type);
     type_register_static(&nondevice_type);
+    type_register_static(&locked_interface_type);
+    type_register_static(&locked_by_interface_type);
+
+    g_test_add_func("/qdev/properties/locking", test_proplist_lock);
 
     g_test_add_func("/qdev/properties/static/default/subprocess",
                     test_static_prop_subprocess);
