Hi,
ZHANG Huangbin reported a misbehavior in ldapd(8)'s MOD_DELETE
operation when connecting to ldapd(8) with the python-ldap library. The
MOD_DELETE operation always deletes all values of an attribute and not
only those specified to be deleted in the request. (Mails from Zhang
Huangbin to bugs@ on May 18, 2016 and December 30, 2016).
I could reproduce this connecting to ldapd(8) with the openLDAP client
tools.
Looking at the source, I found these issues (suggested fixes in
parentheses, tentative patch attached):
- in modify.c:ldap_modify(), in case LDAP_MOD_DELETE there was a check
for BER_TYPE_SET, however
1. AttributeValues are always in a set, even if it is empty
(PartialAttribute, see RFC4511, Section 4.1.7), so that check
couldn't have worked, even if the right variable had been checked.
2. The `vals' variable has a value of SET, however the variable
checked, `vals->be_sup' is already an element of the set, that is,
either it has a type of EOC (when there are no attribute values),
or it has a type of OCTETSTRING and contains the first attribute
value. (Look for a type of BER_TYPE_OCTETSTRING instead).
- in attributes.c:ldap_del_values()
1. the elements inspected (variables `vk' and `xk') are not those
containing the attribute values; the attribute values are in `v'
and `x', `xk' and `vk' are (probably) uninitialized. (Use `v' and
`x' instead.)
2. When freeing the element found, current `v' is freed, and
`v->be_next' has no meaning anymore. (Use `next' variable to save
the pointer.)
3. Always setting `prev' to `v' is wrong when an element has been
removed. (Set a flag if element is removed and re-set `prev' only
if the flag isn't set.)
- in ber.c:ber_free_elements() the current and all following elements
are freed. (Add ber_free_element() which only frees only the current
element and use this in attributes.c:ldap_del_values().)
The patch works for 6.0 plus patches; those three files haven't been
touched since before 6.0, though. (I don't have a -current installation
at the moment. Will make up asap.)
Best Regards
Robert
Index: attributes.c
===================================================================
RCS file: /cvs/src/usr.sbin/ldapd/attributes.c,v
retrieving revision 1.3
diff -u -p -r1.3 attributes.c
--- attributes.c 19 Oct 2010 09:34:41 -0000 1.3
+++ attributes.c 9 Jan 2017 18:47:39 -0000
@@ -206,9 +206,9 @@ int
ldap_del_values(struct ber_element *elm, struct ber_element *vals)
{
char *attr;
- struct ber_element *old_vals, *v, *x, *vk, *xk, *prev;
+ struct ber_element *old_vals, *v, *x, *prev, *next;
struct ber_element *removed;
-
+ int removed_p;
assert(elm);
assert(vals);
assert(vals->be_sub);
@@ -219,19 +219,25 @@ ldap_del_values(struct ber_element *elm,
}
prev = old_vals;
- for (v = old_vals->be_sub; v; v = v->be_next) {
- vk = v->be_sub;
+ removed_p = 0;
+ for (v = old_vals->be_sub; v; v = next) {
+ next = v->be_next;
+
for (x = vals->be_sub; x; x = x->be_next) {
- xk = x->be_sub;
- if (xk && vk->be_len == xk->be_len &&
- memcmp(vk->be_val, xk->be_val, xk->be_len) == 0) {
+ if (x && v->be_len == x->be_len &&
+ memcmp(v->be_val, x->be_val, x->be_len) == 0) {
removed = ber_unlink_elements(prev);
ber_link_elements(prev, removed->be_next);
- ber_free_elements(removed);
+ ber_free_element(removed);
+ removed_p = 1;
break;
}
}
- prev = v;
+ if (removed_p) {
+ removed_p = 0;
+ } else {
+ prev = v;
+ }
}
return 0;
Index: ber.c
===================================================================
RCS file: /cvs/src/usr.sbin/ldapd/ber.c,v
retrieving revision 1.11
diff -u -p -r1.11 ber.c
--- ber.c 24 Dec 2015 17:47:57 -0000 1.11
+++ ber.c 9 Jan 2017 18:47:39 -0000
@@ -826,6 +826,19 @@ ber_read_elements(struct ber *ber, struc
}
void
+ber_free_element(struct ber_element *root)
+{
+ if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET))
+ ber_free_elements(root->be_sub);
+ if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING ||
+ root->be_encoding == BER_TYPE_BITSTRING ||
+ root->be_encoding == BER_TYPE_OBJECT))
+ free(root->be_val);
+ free(root);
+}
+
+void
ber_free_elements(struct ber_element *root)
{
if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
Index: ber.h
===================================================================
RCS file: /cvs/src/usr.sbin/ldapd/ber.h,v
retrieving revision 1.1
diff -u -p -r1.1 ber.h
--- ber.h 31 May 2010 17:36:31 -0000 1.1
+++ ber.h 9 Jan 2017 18:47:39 -0000
@@ -120,6 +120,7 @@ ssize_t ber_get_writebuf(struct ber *
int ber_write_elements(struct ber *, struct ber_element *);
void ber_set_readbuf(struct ber *, void *, size_t);
struct ber_element *ber_read_elements(struct ber *, struct ber_element *);
+void ber_free_element(struct ber_element *);
void ber_free_elements(struct ber_element *);
size_t ber_calc_len(struct ber_element *);
void ber_set_application(struct ber *,
Index: modify.c
===================================================================
RCS file: /cvs/src/usr.sbin/ldapd/modify.c,v
retrieving revision 1.17
diff -u -p -r1.17 modify.c
--- modify.c 24 Dec 2015 17:47:57 -0000 1.17
+++ modify.c 9 Jan 2017 18:47:39 -0000
@@ -295,11 +295,19 @@ ldap_modify(struct request *req)
}
break;
case LDAP_MOD_DELETE:
+ /* we're already in the "SET OF value
+ AttributeValue" (see RFC2411 section 4.1.7)
+ have either EOC, so all values for the
+ attribute gets deleted, or we have a
+ (first) octetstring (there is one for each
+ AttributeValue to be deleted) */
if (vals->be_sub &&
- vals->be_sub->be_type == BER_TYPE_SET)
+ vals->be_sub->be_type == BER_TYPE_OCTETSTRING) {
ldap_del_values(a, vals);
- else
+ }
+ else {
ldap_del_attribute(entry, attr);
+ }
break;
case LDAP_MOD_REPLACE:
if (vals->be_sub != NULL &&