]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
CVE-2020-25717 winbindd: add generic wb_parent_idmap_setup_send/recv() helpers
authorStefan Metzmacher <metze@samba.org>
Fri, 11 Sep 2020 10:16:00 +0000 (12:16 +0200)
committerJule Anger <janger@samba.org>
Mon, 8 Nov 2021 09:52:09 +0000 (10:52 +0100)
This is more or less a copy of wb_xids2sids_init_dom_maps_send/recv,
but it's more generic and doesn't imply global state.

It also closes a initialization race by using a tevent_queue to
serialize the calls.

In the next commits we'll replace wb_xids2sids_init_dom_maps_send/recv.

We'll also use the new function in the wb_sids2xids code.

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

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14556

(cherry picked from commit 209e81a2ea8c972ee57e2f0c9579da843c0e2ac7)

source3/winbindd/winbindd.h
source3/winbindd/winbindd_idmap.c
source3/winbindd/winbindd_proto.h

index a72d6aa7830a738d76f63442fb6a7b86ecd79c0e..480ba4f1282296c74bd43aebf7c048fb09959b07 100644 (file)
@@ -189,6 +189,19 @@ struct winbindd_domain {
        struct winbindd_domain *prev, *next;
 };
 
+struct wb_parent_idmap_config_dom {
+       unsigned low_id;
+       unsigned high_id;
+       const char *name;
+       struct dom_sid sid;
+};
+
+struct wb_parent_idmap_config {
+       struct tevent_queue *queue;
+       uint32_t num_doms;
+       struct wb_parent_idmap_config_dom *doms;
+};
+
 struct wb_acct_info {
        const char *acct_name; /* account name */
        const char *acct_desc; /* account name */
index bd5f3a67aadeec0415622082fd913fb7b950d535..487f27fd94dde669c609fadc4944f52da4475aa3 100644 (file)
 
 #include "includes.h"
 #include "winbindd.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_WINBIND
 
 static struct winbindd_child static_idmap_child;
 
+/*
+ * Map idmap ranges to domain names, taken from smb.conf. This is
+ * stored in the parent winbind and used to assemble xids2sids/sids2xids calls
+ * into per-idmap-domain chunks.
+ */
+static struct wb_parent_idmap_config static_parent_idmap_config;
+
 struct winbindd_child *idmap_child(void)
 {
        return &static_idmap_child;
@@ -73,3 +82,308 @@ void init_idmap_child(void)
                    idmap_dispatch_table,
                    "log.winbindd", "idmap");
 }
+
+struct wb_parent_idmap_setup_state {
+       struct tevent_context *ev;
+       struct wb_parent_idmap_config *cfg;
+       size_t dom_idx;
+};
+
+static void wb_parent_idmap_setup_cleanup(struct tevent_req *req,
+                                         enum tevent_req_state req_state)
+{
+       struct wb_parent_idmap_setup_state *state =
+               tevent_req_data(req,
+               struct wb_parent_idmap_setup_state);
+
+       if (req_state == TEVENT_REQ_DONE) {
+               state->cfg = NULL;
+               return;
+       }
+
+       if (state->cfg == NULL) {
+               return;
+       }
+
+       state->cfg->num_doms = 0;
+       TALLOC_FREE(state->cfg->doms);
+       state->cfg = NULL;
+}
+
+static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq);
+static bool wb_parent_idmap_setup_scan_config(const char *domname,
+                                             void *private_data);
+static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req);
+static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev)
+{
+       struct tevent_req *req = NULL;
+       struct wb_parent_idmap_setup_state *state = NULL;
+       struct tevent_req *subreq = NULL;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct wb_parent_idmap_setup_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       *state = (struct wb_parent_idmap_setup_state) {
+               .ev = ev,
+               .cfg = &static_parent_idmap_config,
+               .dom_idx = 0,
+       };
+
+       if (state->cfg->queue == NULL) {
+               state->cfg->queue = tevent_queue_create(NULL,
+                                               "wb_parent_idmap_config_queue");
+               if (tevent_req_nomem(state->cfg->queue, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       }
+
+       subreq = tevent_queue_wait_send(state, state->ev, state->cfg->queue);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq,
+                               wb_parent_idmap_setup_queue_wait_done,
+                               req);
+
+       return req;
+}
+
+static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct wb_parent_idmap_setup_state *state =
+               tevent_req_data(req,
+               struct wb_parent_idmap_setup_state);
+       bool ok;
+
+       /*
+        * Note we don't call TALLOC_FREE(subreq) here in order to block the
+        * queue until tevent_req_received() in wb_parent_idmap_setup_recv()
+        * will destroy it implicitly.
+        */
+       ok = tevent_queue_wait_recv(subreq);
+       if (!ok) {
+               DBG_ERR("tevent_queue_wait_recv() failed\n");
+               tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+               return;
+       }
+
+       if (state->cfg->num_doms != 0) {
+               /*
+                * If we're not the first one we're done.
+                */
+               tevent_req_done(req);
+               return;
+       }
+
+       /*
+        * From this point we start changing state->cfg,
+        * which is &static_parent_idmap_config,
+        * so we better setup a cleanup function
+        * to undo the changes on failure.
+        */
+       tevent_req_set_cleanup_fn(req, wb_parent_idmap_setup_cleanup);
+
+       /*
+        * Put the passdb idmap domain first. We always need to try
+        * there first.
+        */
+       state->cfg->doms = talloc_zero_array(NULL,
+                                            struct wb_parent_idmap_config_dom,
+                                            1);
+       if (tevent_req_nomem(state->cfg->doms, req)) {
+               return;
+       }
+       state->cfg->doms[0].low_id = 0;
+       state->cfg->doms[0].high_id = UINT_MAX;
+       state->cfg->doms[0].name = talloc_strdup(state->cfg->doms,
+                                                get_global_sam_name());
+       if (tevent_req_nomem(state->cfg->doms[0].name, req)) {
+               return;
+       }
+       state->cfg->num_doms += 1;
+
+       lp_scan_idmap_domains(wb_parent_idmap_setup_scan_config, req);
+       if (!tevent_req_is_in_progress(req)) {
+               return;
+       }
+
+       wb_parent_idmap_setup_lookupname_next(req);
+}
+
+static bool wb_parent_idmap_setup_scan_config(const char *domname,
+                                             void *private_data)
+{
+       struct tevent_req *req =
+               talloc_get_type_abort(private_data,
+               struct tevent_req);
+       struct wb_parent_idmap_setup_state *state =
+               tevent_req_data(req,
+               struct wb_parent_idmap_setup_state);
+       struct wb_parent_idmap_config_dom *map = NULL;
+       size_t i;
+       const char *range;
+       unsigned low_id, high_id;
+       int ret;
+
+       range = idmap_config_const_string(domname, "range", NULL);
+       if (range == NULL) {
+               DBG_DEBUG("No range for domain %s found\n", domname);
+               return false;
+       }
+
+       ret = sscanf(range, "%u - %u", &low_id, &high_id);
+       if (ret != 2) {
+               DBG_DEBUG("Invalid range spec \"%s\" for domain %s\n",
+                         range, domname);
+               return false;
+       }
+
+       if (low_id > high_id) {
+               DBG_DEBUG("Invalid range %u - %u for domain %s\n",
+                         low_id, high_id, domname);
+               return false;
+       }
+
+       for (i=0; i<state->cfg->num_doms; i++) {
+               if (strequal(domname, state->cfg->doms[i].name)) {
+                       map = &state->cfg->doms[i];
+                       break;
+               }
+       }
+
+       if (map == NULL) {
+               struct wb_parent_idmap_config_dom *tmp;
+               char *name;
+
+               name = talloc_strdup(state, domname);
+               if (name == NULL) {
+                       DBG_ERR("talloc failed\n");
+                       return false;
+               }
+
+               tmp = talloc_realloc(
+                       NULL, state->cfg->doms, struct wb_parent_idmap_config_dom,
+                       state->cfg->num_doms+1);
+               if (tmp == NULL) {
+                       DBG_ERR("talloc failed\n");
+                       return false;
+               }
+               state->cfg->doms = tmp;
+
+               map = &state->cfg->doms[state->cfg->num_doms];
+               state->cfg->num_doms += 1;
+               ZERO_STRUCTP(map);
+               map->name = talloc_move(state->cfg->doms, &name);
+       }
+
+       map->low_id = low_id;
+       map->high_id = high_id;
+
+       return false;
+}
+
+static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req)
+{
+       struct wb_parent_idmap_setup_state *state =
+               tevent_req_data(req,
+               struct wb_parent_idmap_setup_state);
+       struct wb_parent_idmap_config_dom *dom =
+               &state->cfg->doms[state->dom_idx];
+       struct tevent_req *subreq = NULL;
+
+ next_domain:
+       if (state->dom_idx == state->cfg->num_doms) {
+               tevent_req_done(req);
+               return;
+       }
+
+       if (strequal(dom->name, "*")) {
+               state->dom_idx++;
+               goto next_domain;
+       }
+
+       subreq = wb_lookupname_send(state,
+                                   state->ev,
+                                   dom->name,
+                                   dom->name,
+                                   "",
+                                   LOOKUP_NAME_NO_NSS);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq,
+                               wb_parent_idmap_setup_lookupname_done,
+                               req);
+}
+
+static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct wb_parent_idmap_setup_state *state =
+               tevent_req_data(req,
+               struct wb_parent_idmap_setup_state);
+       struct wb_parent_idmap_config_dom *dom =
+               &state->cfg->doms[state->dom_idx];
+       enum lsa_SidType type;
+       NTSTATUS status;
+
+       status = wb_lookupname_recv(subreq, &dom->sid, &type);
+       TALLOC_FREE(subreq);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("Lookup domain name '%s' failed '%s'\n",
+                       dom->name,
+                       nt_errstr(status));
+
+               state->dom_idx++;
+               wb_parent_idmap_setup_lookupname_next(req);
+               return;
+       }
+
+       if (type != SID_NAME_DOMAIN) {
+               struct dom_sid_buf buf;
+
+               DBG_ERR("SID %s for idmap domain name '%s' "
+                       "not a domain SID\n",
+                       dom_sid_str_buf(&dom->sid, &buf),
+                       dom->name);
+
+               ZERO_STRUCT(dom->sid);
+       }
+
+       state->dom_idx++;
+       wb_parent_idmap_setup_lookupname_next(req);
+
+       return;
+}
+
+NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req,
+                                   const struct wb_parent_idmap_config **_cfg)
+{
+       const struct wb_parent_idmap_config *cfg = &static_parent_idmap_config;
+       NTSTATUS status;
+
+       *_cfg = NULL;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+
+       /*
+        * Note state->cfg is already set to NULL by
+        * wb_parent_idmap_setup_cleanup()
+        */
+       *_cfg = cfg;
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
index 97c38018aac55568e75efc03ab922a6e17e3544b..8923bb3124f95e55af47c843a2b7af55cba0a4a5 100644 (file)
@@ -364,6 +364,11 @@ NTSTATUS winbindd_print_groupmembers(struct db_context *members,
 
 /* The following definitions come from winbindd/winbindd_idmap.c  */
 
+struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev);
+NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req,
+                                   const struct wb_parent_idmap_config **_cfg);
+
 void init_idmap_child(void);
 struct winbindd_child *idmap_child(void);
 bool is_idmap_child(const struct winbindd_child *child);