Commit 107883df authored by Stefan Metzmacher's avatar Stefan Metzmacher Committed by Karolin Seeger

dbcheck: detect the change after deletion bug

Old versions of 'samba-tool dbcheck' could reanimate
deleted objects, when running at the same time as the
tombstone garbage collection.

When the (deleted) parent of a deleted object
(with the DISALLOW_MOVE_ON_DELETE bit in systemFlags),
is removed before the object itself, dbcheck moved
it in the LostAndFound[Config] subtree of the partition
as an originating change. That means that the object
will be in tombstone state again for 180 days on the local
DC. And other DCs fail to replicate the object as
it's already removed completely there and the replication
only gives the name and lastKnownParent attributes, because
all other attributes should already be known to the other DC.

BUG: 's avatarStefan Metzmacher <>
Reviewed-by: 's avatarAndrew Bartlett <>
(cherry picked from commit a1658b30)
parent 860b04aa
...@@ -101,6 +101,7 @@ class dbcheck(object): ...@@ -101,6 +101,7 @@ class dbcheck(object):
self.fix_missing_deleted_objects = False self.fix_missing_deleted_objects = False
self.fix_replica_locations = False self.fix_replica_locations = False
self.fix_missing_rid_set_master = False self.fix_missing_rid_set_master = False
self.fix_changes_after_deletion_bug = False
self.dn_set = set() self.dn_set = set()
self.link_id_cache = {} self.link_id_cache = {}
...@@ -189,6 +190,14 @@ class dbcheck(object): ...@@ -189,6 +190,14 @@ class dbcheck(object):
else: else:
self.rid_set_dn = None self.rid_set_dn = None
ntds_service_dn = "CN=Directory Service,CN=Windows NT,CN=Services,%s" % \
res =,
self.tombstoneLifetime = int(res[0]["tombstoneLifetime"][0])
self.compatibleFeatures = [] self.compatibleFeatures = []
self.requiredFeatures = [] self.requiredFeatures = []
...@@ -1733,6 +1742,100 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) ...@@ -1733,6 +1742,100 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))"Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))"Fixed attribute '%s' of '%s'\n" % (sd_attr, dn))
self.samdb.set_session_info(self.system_session_info) self.samdb.set_session_info(self.system_session_info)
def find_changes_after_deletion(self, repl_val):
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl_val)
isDeleted = self.find_repl_attid(repl, drsuapi.DRSUAPI_ATTID_isDeleted)
delete_time = samba.nttime2unix(isDeleted.originating_change_time)
tombstone_delta = self.tombstoneLifetime * (24 * 60 * 60)
found = []
for o in repl.ctr.array:
if o.attid == drsuapi.DRSUAPI_ATTID_isDeleted:
if o.local_usn <= isDeleted.local_usn:
if o.originating_change_time <= isDeleted.originating_change_time:
change_time = samba.nttime2unix(o.originating_change_time)
delta = change_time - delete_time
if delta <= tombstone_delta:
# If the modification happened after the tombstone lifetime
# has passed, we have a bug as the object might be deleted
# already on other DCs and won't be able to replicate
# back
return found, isDeleted
def has_changes_after_deletion(self, dn, repl_val):
found, isDeleted = self.find_changes_after_deletion(repl_val)
if len(found) == 0:
return False
def report_attid(o):
attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
except KeyError:
attname = "<unknown:0x%x08x>" % o.attid"%s: attid=0x%08x version=%d invocation=%s usn=%s (local=%s) at %s" % (
attname, o.attid, o.version,
time.ctime(samba.nttime2unix(o.originating_change_time))))"ERROR: object %s, has changes after deletion" % dn)
for o in found:
return True
def err_changes_after_deletion(self, dn, repl_val):
found, isDeleted = self.find_changes_after_deletion(repl_val)
in_schema_nc = dn.is_child_of(self.schema_dn)
rdn_attr = dn.get_rdn_name()
rdn_attid = self.samdb_schema.get_attid_from_lDAPDisplayName(rdn_attr,
unexpected = []
for o in found:
if o.attid == rdn_attid:
if o.attid == drsuapi.DRSUAPI_ATTID_name:
if o.attid == drsuapi.DRSUAPI_ATTID_lastKnownParent:
attname = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
except KeyError:
attname = "<unknown:0x%x08x>" % o.attid
if len(unexpected) > 0:'Unexpeted attributes: %s' % ",".join(unexpected))'Not fixing changes after deletion bug')
if not self.confirm_all('Delete broken tombstone object %s deleted %s days ago?' % (
dn, self.tombstoneLifetime), 'fix_changes_after_deletion_bug'):'Not fixing changes after deletion bug')
if self.do_delete(dn, ["relax:0"],
"Failed to remove DN %s" % dn):"Removed DN %s" % dn)
def has_replmetadata_zero_invocationid(self, dn, repl_meta_data): def has_replmetadata_zero_invocationid(self, dn, repl_meta_data):
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
...@@ -2115,6 +2218,12 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) ...@@ -2115,6 +2218,12 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
if str(attrname).lower() == 'replpropertymetadata': if str(attrname).lower() == 'replpropertymetadata':
repl_meta_data_val = obj[attrname][0] repl_meta_data_val = obj[attrname][0]
if isDeleted and repl_meta_data_val:
if self.has_changes_after_deletion(dn, repl_meta_data_val):
error_count += 1
self.err_changes_after_deletion(dn, repl_meta_data_val)
return error_count
for attrname in obj: for attrname in obj:
if attrname == 'dn' or attrname == "distinguishedName": if attrname == 'dn' or attrname == "distinguishedName":
continue continue
Checking 232 objects Checking 232 objects
ERROR: missing GUID component for lastKnownParent in object CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp - OU=removed,DC=release-4-5-0-pre1,DC=samba,DC=corp ERROR: object CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp, has changes after deletion
unable to find object for DN OU=removed,DC=release-4-5-0-pre1,DC=samba,DC=corp - (No such Base DN: OU=removed,DC=release-4-5-0-pre1,DC=samba,DC=corp) isDeleted: attid=0x00020030 version=1 invocation=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d usn=3746 (local=3746) at Wed Jun 29 04:36:39 2016
WARNING: no target object found for GUID component link lastKnownParent in deleted object CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp - OU=removed,DC=release-4-5-0-pre1,DC=samba,DC=corp name: attid=0x00090001 version=4 invocation=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d usn=3772 (local=3772) at Mon Mar 11 13:28:24 2019
Not removing dangling one-way link on deleted object (tombstone garbage collection in progress?) lastKnownParent: attid=0x0009030d version=3 invocation=4e4496a3-7fb8-4f97-8a33-d238db8b5e2d usn=3773 (local=3773) at Mon Mar 11 13:28:24 2019
ERROR: wrong dn[CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp] cn='fred\nDEL:2301a64c-8765-4321-851e-12d4a711cfb4' name=b'fred\nDEL:2301a64c-8765-4321-851e-12d4a711cfb4' new_dn[CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=Deleted Objects,DC=release-4-5-0-pre1,DC=samba,DC=corp] Delete broken tombstone object CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp deleted 180 days ago? [YES]
Rename CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp to CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=Deleted Objects,DC=release-4-5-0-pre1,DC=samba,DC=corp? [YES] Removed DN CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp
Renamed CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=LostAndFound,DC=release-4-5-0-pre1,DC=samba,DC=corp into CN=fred\0ADEL:2301a64c-8765-4321-851e-12d4a711cfb4,CN=Deleted Objects,DC=release-4-5-0-pre1,DC=samba,DC=corp Checked 232 objects (1 errors)
Checked 232 objects (2 errors)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment