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 &&

Reply via email to