CVE-2021-3670 ldb: Confirm the request has not yet timed out in ldb filter processing
authorAndrew Bartlett <abartlet@samba.org>
Mon, 27 Sep 2021 03:47:46 +0000 (16:47 +1300)
committerJule Anger <janger@samba.org>
Thu, 2 Dec 2021 10:33:13 +0000 (10:33 +0000)
The LDB filter processing is where the time is spent in the LDB stack
but the timeout event will not get run while this is ongoing, so we
must confirm we have not yet timed out manually.

RN: Ensure that the LDB request has not timed out during filter processing
as the LDAP server MaxQueryDuration is otherwise not honoured.

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

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit 1d5b155619bc532c46932965b215bd73a920e56f)

lib/ldb/ldb_key_value/ldb_kv.c
lib/ldb/ldb_key_value/ldb_kv.h
lib/ldb/ldb_key_value/ldb_kv_index.c
lib/ldb/ldb_key_value/ldb_kv_search.c
selftest/knownfail.d/ldap-timeout [deleted file]

index ed0f760b5a26d51bb4c7945dd09da5bbb16ba0ec..aea6f0c1be0df4823f7ae06684ff71a093a6239b 100644 (file)
@@ -2078,6 +2078,8 @@ static int ldb_kv_handle_request(struct ldb_module *module,
                }
        }
 
+       ac->timeout_timeval = tv;
+
        /* set a spy so that we do not try to use the request context
         * if it is freed before ltdb_callback fires */
        ac->spy = talloc(req, struct ldb_kv_req_spy);
index f9dffae2dcf05bdbf3c7bc91a189cdd2afc03a55..ac474b04b4cd65cb959d092ecd02f3404af14384 100644 (file)
@@ -152,6 +152,16 @@ struct ldb_kv_context {
        struct ldb_module *module;
        struct ldb_request *req;
 
+       /*
+        * Required as we might not get to the event loop before the
+        * timeout, so we need some old-style cooperative multitasking
+        * here.
+        */
+       struct timeval timeout_timeval;
+
+       /* Used to throttle calls to gettimeofday() */
+       size_t timeout_counter;
+
        bool request_terminated;
        struct ldb_kv_req_spy *spy;
 
index 1cc042aa84fbf44661574f0e4a69d2f4523e8e9d..d70e5f619efc5d95ee5a713d64adb8d3f286cd0f 100644 (file)
@@ -2352,6 +2352,47 @@ static int ldb_kv_index_filter(struct ldb_kv_private *ldb_kv,
        for (i = 0; i < num_keys; i++) {
                int ret;
                bool matched;
+
+               /*
+                * Check the time every 64 records, to reduce calls to
+                * gettimeofday().  This is a compromise, not all
+                * calls to ldb_match_message() will take the same
+                * time, most will run quickly but by luck it might be
+                * possible to have 64 records that are slow, doing a
+                * recursive search via LDAP_MATCHING_RULE_IN_CHAIN.
+                *
+                * Thankfully this is after index processing so only
+                * on the subset that matches some index (but still
+                * possibly a big one like objectclass=user)
+                */
+               if (i % 64 == 0) {
+                       struct timeval now = tevent_timeval_current();
+                       int timeval_cmp = tevent_timeval_compare(&ac->timeout_timeval,
+                                                                &now);
+
+                       /*
+                        * The search has taken too long.  This is the
+                        * most likely place for our time to expire,
+                        * as we are checking the records after the
+                        * index set intersection.  This is now the
+                        * slow process of checking if the records
+                        * actually match.
+                        *
+                        * The tevent based timeout is not likely to
+                        * be hit, sadly, as we don't run an event
+                        * loop.
+                        *
+                        * While we are indexed and most of the work
+                        * should have been done already, the
+                        * ldb_match_* calls can be quite expensive if
+                        * the caller uses LDAP_MATCHING_RULE_IN_CHAIN
+                        */
+                       if (timeval_cmp <= 0) {
+                               talloc_free(keys);
+                               return LDB_ERR_TIME_LIMIT_EXCEEDED;
+                       }
+               }
+
                msg = ldb_msg_new(ac);
                if (!msg) {
                        talloc_free(keys);
index a0e1762bc9027113b0395ed0ab0931ed883b5099..46031b99c16c9410d85ed622de01f670801ccdce 100644 (file)
@@ -314,7 +314,8 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv,
        struct ldb_context *ldb;
        struct ldb_kv_context *ac;
        struct ldb_message *msg, *filtered_msg;
-       int ret;
+       struct timeval now;
+       int ret, timeval_cmp;
        bool matched;
 
        ac = talloc_get_type(state, struct ldb_kv_context);
@@ -341,6 +342,36 @@ static int search_func(_UNUSED_ struct ldb_kv_private *ldb_kv,
                return 0;
        }
 
+       /*
+        * Check the time every 64 records, to reduce calls to
+        * gettimeofday().  This is a compromise, not all calls to
+        * ldb_match_message() will take the same time, most will fail
+        * quickly but by luck it might be possible to have 64 records
+        * that are slow, doing a recursive search via
+        * LDAP_MATCHING_RULE_IN_CHAIN.
+        */
+       if (ac->timeout_counter++ % 64 == 0) {
+               now = tevent_timeval_current();
+               timeval_cmp = tevent_timeval_compare(&ac->timeout_timeval,
+                                                    &now);
+
+               /*
+                * The search has taken too long.  This is the most
+                * likely place for our time to expire, as we are in
+                * an un-indexed search and we return the data from
+                * within this loop.  The tevent based timeout is not
+                * likely to be hit, sadly.
+                *
+                * ldb_match_msg_error() can be quite expensive if a
+                * LDAP_MATCHING_RULE_IN_CHAIN extended match was
+                * specified.
+                */
+               if (timeval_cmp <= 0) {
+                       ac->error = LDB_ERR_TIME_LIMIT_EXCEEDED;
+                       return -1;
+               }
+       }
+
        msg = ldb_msg_new(ac);
        if (!msg) {
                ac->error = LDB_ERR_OPERATIONS_ERROR;
diff --git a/selftest/knownfail.d/ldap-timeout b/selftest/knownfail.d/ldap-timeout
deleted file mode 100644 (file)
index 378ca1f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-samba4.ldap.large_ldap\..*\.python\(.*\).__main__.LargeLDAPTest.test_timeout\(.*\)
\ No newline at end of file