Skip to content

Commit

Permalink
dbcheck: Add explict tests for unknown and unsorted attributeID values
Browse files Browse the repository at this point in the history
Unknown attributeID values would cause an exception previously, and
unsorted attributes cause a failure to replicate with Samba 4.2.

In commit 61b9788 we started
to sort these values correctly, but previous versions of Samba
did not sort them correctly (we sorted high-bit-set values as
negative), and then after 9c9df40
we stoped accepting these.

To ensure we are allowed to make this unusual change to the
replPropertyMetaData, a new OID is allocated and checked
for in repl_meta_data.c

BUG: https://bugzilla.samba.org/show_bug.cgi?id=10973

Signed-off-by: Andrew Bartlett <[email protected]>
Reviewed-by: Stefan Metzmacher <[email protected]>
  • Loading branch information
abartlet authored and metze-samba committed Aug 24, 2015
1 parent e3cf25b commit 2766bad
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 13 deletions.
64 changes: 60 additions & 4 deletions python/samba/dbchecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from samba import dsdb
from samba import common
from samba.dcerpc import misc
from samba.dcerpc import drsuapi
from samba.ndr import ndr_unpack, ndr_pack
from samba.dcerpc import drsblobs
from samba.common import dsdb_Dn
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
self.move_to_lost_and_found = False
self.fix_instancetype = False
self.fix_replmetadata_zero_invocationid = False
self.fix_replmetadata_unsorted_attid = False
self.fix_deleted_deleted_objects = False
self.fix_dn = False
self.fix_base64_userparameters = False
Expand Down Expand Up @@ -698,18 +700,21 @@ def get_originating_time(self, val, attid):
return 0

def process_metadata(self, val):
'''Read metadata properties and list attributes in it'''
'''Read metadata properties and list attributes in it.
raises KeyError if the attid is unknown.'''

list_att = []
list_attid = []

repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
obj = repl.ctr

for o in repl.ctr.array:
att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
list_att.append(att.lower())
list_attid.append(o.attid)

return list_att
return (list_att, list_attid)


def fix_metadata(self, dn, attr):
Expand Down Expand Up @@ -989,11 +994,52 @@ def err_replmetadata_zero_invocationid(self, dn, attr, repl_meta_data):
nmsg = ldb.Message()
nmsg.dn = dn
nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
if self.do_modify(nmsg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
"local_oid:1.3.6.1.4.1.7165.4.3.14:0"],
"Failed to fix attribute %s" % attr):
self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))


def err_replmetadata_unknown_attid(self, dn, attr, repl_meta_data):
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
str(repl_meta_data))
ctr = repl.ctr
for o in ctr.array:
# Search for an invalid attid
try:
att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
except KeyError:
self.report('ERROR: attributeID 0X%0X is not known in our schema, not fixing %s on %s\n' % (o.attid, attr, dn))
return


def err_replmetadata_unsorted_attid(self, dn, attr, repl_meta_data):
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
str(repl_meta_data))
ctr = repl.ctr
found = False

self.report('ERROR: unsorted attributeID values in %s on %s\n' % (attr, dn))
if not self.confirm_all('Fix %s on %s by sorting the attribute list?'
% (attr, dn), 'fix_replmetadata_unsorted_attid'):
self.report('Not fixing %s on %s\n' % (attr, dn))
return

# Sort the array, except for the last element
ctr.array[:-1] = sorted(ctr.array[:-1], key=lambda o: o.attid)

replBlob = ndr_pack(repl)

nmsg = ldb.Message()
nmsg.dn = dn
nmsg[attr] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, attr)
if self.do_modify(nmsg, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA,
"local_oid:1.3.6.1.4.1.7165.4.3.14:0",
"local_oid:1.3.6.1.4.1.7165.4.3.25:0"],
"Failed to fix attribute %s" % attr):
self.report("Fixed attribute '%s' of '%s'\n" % (attr, dn))


def is_deleted_deleted_objects(self, obj):
faulty = False
if "description" not in obj:
Expand Down Expand Up @@ -1181,7 +1227,17 @@ def check_object(self, dn, attrs=['*']):
# We don't continue, as we may also have other fixes for this attribute
# based on what other attributes we see.

list_attrs_from_md = self.process_metadata(obj[attrname])
try:
(list_attrs_from_md, list_attid_from_md) = self.process_metadata(obj[attrname])
except KeyError:
error_count += 1
self.err_replmetadata_unknown_attid(dn, attrname, obj[attrname])
continue

if sorted(list_attid_from_md[:-1]) != list_attid_from_md[:-1]:
error_count += 1
self.err_replmetadata_unsorted_attid(dn, attrname, obj[attrname])

got_repl_property_meta_data = True
continue

Expand Down
24 changes: 24 additions & 0 deletions python/samba/tests/dsdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ def test_error_replpropertymetadata(self):
msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])

def test_error_replpropertymetadata_nochange(self):
res = self.samdb.search(expression="cn=Administrator",
scope=ldb.SCOPE_SUBTREE,
attrs=["replPropertyMetaData"])
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
str(res[0]["replPropertyMetaData"]))
replBlob = ndr_pack(repl)
msg = ldb.Message()
msg.dn = res[0].dn
msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])

def test_error_replpropertymetadata_allow_sort(self):
res = self.samdb.search(expression="cn=Administrator",
scope=ldb.SCOPE_SUBTREE,
attrs=["replPropertyMetaData"])
repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
str(res[0]["replPropertyMetaData"]))
replBlob = ndr_pack(repl)
msg = ldb.Message()
msg.dn = res[0].dn
msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
self.samdb.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0", "local_oid:1.3.6.1.4.1.7165.4.3.25:0"])

def test_twoatt_replpropertymetadata(self):
res = self.samdb.search(expression="cn=Administrator",
scope=ldb.SCOPE_SUBTREE,
Expand Down
31 changes: 22 additions & 9 deletions source4/dsdb/samdb/ldb_modules/repl_meta_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
struct ldb_message_element *objectclass_el;
enum urgent_situation situation;
bool rmd_is_provided;
bool rmd_is_just_resorted = false;

if (rename_attrs) {
attrs = rename_attrs;
Expand All @@ -1404,6 +1405,9 @@ static int replmd_update_rpmd(struct ldb_module *module,

if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_OID)) {
rmd_is_provided = true;
if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID)) {
rmd_is_just_resorted = true;
}
} else {
rmd_is_provided = false;
}
Expand All @@ -1422,7 +1426,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
/* In this case the change_replmetadata control was supplied */
/* We check that it's the only attribute that is provided
* (it's a rare case so it's better to keep the code simplier)
* We also check that the highest local_usn is bigger than
* We also check that the highest local_usn is bigger or the same as
* uSNChanged. */
uint64_t db_seq;
if( msg->num_elements != 1 ||
Expand All @@ -1449,7 +1453,6 @@ static int replmd_update_rpmd(struct ldb_module *module,
ldb_dn_get_linearized(msg->dn)));
return LDB_ERR_OPERATIONS_ERROR;
}
*seq_num = find_max_local_usn(omd);

ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs2,
DSDB_FLAG_NEXT_MODULE |
Expand All @@ -1462,12 +1465,22 @@ static int replmd_update_rpmd(struct ldb_module *module,
return ret;
}

db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
if (*seq_num <= db_seq) {
DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)"\
" is less or equal to uSNChanged (max = %lld uSNChanged = %lld)\n",
(long long)*seq_num, (long long)db_seq));
return LDB_ERR_OPERATIONS_ERROR;
if (rmd_is_just_resorted == false) {
*seq_num = find_max_local_usn(omd);

db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);

/*
* The test here now allows for a new
* replPropertyMetaData with no change, if was
* just dbcheck re-sorting the values.
*/
if (*seq_num <= db_seq) {
DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)" \
" is less than uSNChanged (max = %lld uSNChanged = %lld)\n",
(long long)*seq_num, (long long)db_seq));
return LDB_ERR_OPERATIONS_ERROR;
}
}

} else {
Expand Down Expand Up @@ -1547,7 +1560,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
* replmd_update_rpmd_element has done an update if the
* seq_num is set
*/
if (*seq_num != 0) {
if (*seq_num != 0 || rmd_is_just_resorted == true) {
struct ldb_val *md_value;
struct ldb_message_element *el;

Expand Down
6 changes: 6 additions & 0 deletions source4/dsdb/samdb/samdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ struct dsdb_control_password_change {
*/
#define DSDB_CONTROL_RESTORE_TOMBSTONE_OID "1.3.6.1.4.1.7165.4.3.24"

/**
OID used to allow the replacement of replPropertyMetaData.
It is used when the current replmetadata needs only to be re-sorted, but not edited.
*/
#define DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID "1.3.6.1.4.1.7165.4.3.25"

#define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1"
struct dsdb_extended_replicated_object {
struct ldb_message *msg;
Expand Down
1 change: 1 addition & 0 deletions source4/setup/schema_samba4.ldif
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
#Allocated: DSDB_CONTROL_SEC_DESC_PROPAGATION_OID 1.3.6.1.4.1.7165.4.3.21
#Allocated: DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID 1.3.6.1.4.1.7165.4.3.23
#Allocated: DSDB_CONTROL_RESTORE_TOMBSTONE_OID 1.3.6.1.4.1.7165.4.3.24
#Allocated: DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID 1.3.6.1.4.1.7165.4.3.25

# Extended 1.3.6.1.4.1.7165.4.4.x
#Allocated: DSDB_EXTENDED_REPLICATED_OBJECTS_OID 1.3.6.1.4.1.7165.4.4.1
Expand Down

0 comments on commit 2766bad

Please sign in to comment.