aboutsummaryrefslogtreecommitdiffstats
path: root/main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch
diff options
context:
space:
mode:
Diffstat (limited to 'main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch')
-rw-r--r--main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch351
1 files changed, 351 insertions, 0 deletions
diff --git a/main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch b/main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch
new file mode 100644
index 0000000000..2a647fa024
--- /dev/null
+++ b/main/samba/0010-CVE-2018-10919-acl_read-Fix-unauthorized-attribute-a.patch
@@ -0,0 +1,351 @@
+From 9b17ce9a1f46e8519302eb6ec72f1104560bf953 Mon Sep 17 00:00:00 2001
+From: Tim Beale <timbeale@catalyst.net.nz>
+Date: Fri, 20 Jul 2018 15:42:36 +1200
+Subject: [PATCH] CVE-2018-10919 acl_read: Fix unauthorized attribute access
+ via searches
+
+A user that doesn't have access to view an attribute can still guess the
+attribute's value via repeated LDAP searches. This affects confidential
+attributes, as well as ACLs applied to an object/attribute to deny
+access.
+
+Currently the code will hide objects if the attribute filter contains an
+attribute they are not authorized to see. However, the code still
+returns objects as results if confidential attribute is in the search
+expression itself, but not in the attribute filter.
+
+To fix this problem we have to check the access rights on the attributes
+in the search-tree, as well as the attributes returned in the message.
+
+Points of note:
+- I've preserved the existing dirsync logic (the dirsync module code
+ suppresses the result as long as the replPropertyMetaData attribute is
+ removed). However, there doesn't appear to be any test that highlights
+ that this functionality is required for dirsync.
+- To avoid this fix breaking the acl.py tests, we need to still permit
+ searches like 'objectClass=*', even though we don't have Read Property
+ access rights for the objectClass attribute. The logic that Windows
+ uses does not appear to be clearly documented, so I've made a best
+ guess that seems to mirror Windows behaviour.
+
+BUG: https://bugzilla.samba.org/show_bug.cgi?id=13434
+
+Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
+---
+ selftest/knownfail.d/acl | 1 -
+ selftest/knownfail.d/confidential_attr | 15 --
+ source4/dsdb/samdb/ldb_modules/acl_read.c | 247 ++++++++++++++++++++++
+ 3 files changed, 247 insertions(+), 16 deletions(-)
+ delete mode 100644 selftest/knownfail.d/acl
+ delete mode 100644 selftest/knownfail.d/confidential_attr
+
+diff --git a/selftest/knownfail.d/acl b/selftest/knownfail.d/acl
+deleted file mode 100644
+index 6772ea1f943..00000000000
+--- a/selftest/knownfail.d/acl
++++ /dev/null
+@@ -1 +0,0 @@
+-^samba4.ldap.acl.python.*test_search7
+diff --git a/selftest/knownfail.d/confidential_attr b/selftest/knownfail.d/confidential_attr
+deleted file mode 100644
+index 7a2f2aada57..00000000000
+--- a/selftest/knownfail.d/confidential_attr
++++ /dev/null
+@@ -1,15 +0,0 @@
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_basic_search\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_acl_override\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_attr_acl_override\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_blanket_oa_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_neutral_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_neutral_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_neutral_attr_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_neutral_cr_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTest.test_search_with_propset_acl_override\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTestDenyAcl.test_search_with_blanket_oa_deny_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTestDenyAcl.test_search_with_deny_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTestDenyAcl.test_search_with_deny_attr_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTestDenyAcl.test_search_with_deny_propset_acl\(ad_dc_ntvfs\)
+-samba4.ldap.confidential_attr.python\(ad_dc_ntvfs\).__main__.ConfidentialAttrTestDirsync.test_search_with_dirsync\(ad_dc_ntvfs\)
+-
+diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c
+index 9607ed05ee7..280845a47a5 100644
+--- a/source4/dsdb/samdb/ldb_modules/acl_read.c
++++ b/source4/dsdb/samdb/ldb_modules/acl_read.c
+@@ -38,6 +38,8 @@
+ #include "param/param.h"
+ #include "dsdb/samdb/ldb_modules/util.h"
+
++/* The attributeSecurityGuid for the Public Information Property-Set */
++#define PUBLIC_INFO_PROPERTY_SET "e48d0154-bcf8-11d1-8702-00c04fb96050"
+
+ struct aclread_context {
+ struct ldb_module *module;
+@@ -262,6 +264,219 @@ static uint32_t get_attr_access_mask(const struct dsdb_attribute *attr,
+ return access_mask;
+ }
+
++/* helper struct for traversing the attributes in the search-tree */
++struct parse_tree_aclread_ctx {
++ struct aclread_context *ac;
++ TALLOC_CTX *mem_ctx;
++ struct dom_sid *sid;
++ struct ldb_dn *dn;
++ struct security_descriptor *sd;
++ const struct dsdb_class *objectclass;
++ bool suppress_result;
++};
++
++/*
++ * Checks that the user has sufficient access rights to view an attribute
++ */
++static int check_attr_access_rights(TALLOC_CTX *mem_ctx, const char *attr_name,
++ struct aclread_context *ac,
++ struct security_descriptor *sd,
++ const struct dsdb_class *objectclass,
++ struct dom_sid *sid, struct ldb_dn *dn,
++ bool *is_public_info)
++{
++ int ret;
++ const struct dsdb_attribute *attr = NULL;
++ uint32_t access_mask;
++ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
++
++ *is_public_info = false;
++
++ attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, attr_name);
++ if (!attr) {
++ ldb_debug_set(ldb,
++ LDB_DEBUG_TRACE,
++ "acl_read: %s cannot find attr[%s] in schema,"
++ "ignoring\n",
++ ldb_dn_get_linearized(dn), attr_name);
++ return LDB_SUCCESS;
++ }
++
++ /*
++ * If we have no Read Property (RP) rights for a child object, it should
++ * still appear as a visible object in 'objectClass=*' searches,
++ * as long as we have List Contents (LC) rights for it.
++ * This is needed for the acl.py tests (e.g. test_search1()).
++ * I couldn't find the Windows behaviour documented in the specs, so
++ * this is a guess, but it seems to only apply to attributes in the
++ * Public Information Property Set that have the systemOnly flag set to
++ * TRUE. (This makes sense in a way, as it's not disclosive to find out
++ * that a child object has a 'objectClass' or 'name' attribute, as every
++ * object has these attributes).
++ */
++ if (attr->systemOnly) {
++ struct GUID public_info_guid;
++ NTSTATUS status;
++
++ status = GUID_from_string(PUBLIC_INFO_PROPERTY_SET,
++ &public_info_guid);
++ if (!NT_STATUS_IS_OK(status)) {
++ ldb_set_errstring(ldb, "Public Info GUID parse error");
++ return LDB_ERR_OPERATIONS_ERROR;
++ }
++
++ if (GUID_compare(&attr->attributeSecurityGUID,
++ &public_info_guid) == 0) {
++ *is_public_info = true;
++ }
++ }
++
++ access_mask = get_attr_access_mask(attr, ac->sd_flags);
++
++ /* the access-mask should be non-zero. Skip attribute otherwise */
++ if (access_mask == 0) {
++ DBG_ERR("Could not determine access mask for attribute %s\n",
++ attr_name);
++ return LDB_SUCCESS;
++ }
++
++ ret = acl_check_access_on_attribute(ac->module, mem_ctx, sd, sid,
++ access_mask, attr, objectclass);
++
++ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
++ return ret;
++ }
++
++ if (ret != LDB_SUCCESS) {
++ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
++ "acl_read: %s check attr[%s] gives %s - %s\n",
++ ldb_dn_get_linearized(dn), attr_name,
++ ldb_strerror(ret), ldb_errstring(ldb));
++ return ret;
++ }
++
++ return LDB_SUCCESS;
++}
++
++/*
++ * Returns the attribute name for this particular level of a search operation
++ * parse-tree.
++ */
++static const char * parse_tree_get_attr(struct ldb_parse_tree *tree)
++{
++ const char *attr = NULL;
++
++ switch (tree->operation) {
++ case LDB_OP_EQUALITY:
++ case LDB_OP_GREATER:
++ case LDB_OP_LESS:
++ case LDB_OP_APPROX:
++ attr = tree->u.equality.attr;
++ break;
++ case LDB_OP_SUBSTRING:
++ attr = tree->u.substring.attr;
++ break;
++ case LDB_OP_PRESENT:
++ attr = tree->u.present.attr;
++ break;
++ case LDB_OP_EXTENDED:
++ attr = tree->u.extended.attr;
++ break;
++
++ /* we'll check LDB_OP_AND/_OR/_NOT children later on in the walk */
++ default:
++ break;
++ }
++ return attr;
++}
++
++/*
++ * Checks a single attribute in the search parse-tree to make sure the user has
++ * sufficient rights to view it.
++ */
++static int parse_tree_check_attr_access(struct ldb_parse_tree *tree,
++ void *private_context)
++{
++ struct parse_tree_aclread_ctx *ctx = NULL;
++ const char *attr_name = NULL;
++ bool is_public_info = false;
++ int ret;
++
++ ctx = (struct parse_tree_aclread_ctx *)private_context;
++
++ /*
++ * we can skip any further checking if we already know that this object
++ * shouldn't be visible in this user's search
++ */
++ if (ctx->suppress_result) {
++ return LDB_SUCCESS;
++ }
++
++ /* skip this level of the search-tree if it has no attribute to check */
++ attr_name = parse_tree_get_attr(tree);
++ if (attr_name == NULL) {
++ return LDB_SUCCESS;
++ }
++
++ ret = check_attr_access_rights(ctx->mem_ctx, attr_name, ctx->ac,
++ ctx->sd, ctx->objectclass, ctx->sid,
++ ctx->dn, &is_public_info);
++
++ /*
++ * if the user does not have the rights to view this attribute, then we
++ * should not return the object as a search result, i.e. act as if the
++ * object doesn't exist (for this particular user, at least)
++ */
++ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
++
++ /*
++ * We make an exception for attribute=* searches involving the
++ * Public Information property-set. This allows searches like
++ * objectClass=* to return visible objects, even if the user
++ * doesn't have Read Property rights on the attribute
++ */
++ if (tree->operation == LDB_OP_PRESENT && is_public_info) {
++ return LDB_SUCCESS;
++ }
++
++ ctx->suppress_result = true;
++ return LDB_SUCCESS;
++ }
++
++ return ret;
++}
++
++/*
++ * Traverse the search-tree to check that the user has sufficient access rights
++ * to view all the attributes.
++ */
++static int check_search_ops_access(struct aclread_context *ac,
++ TALLOC_CTX *mem_ctx,
++ struct security_descriptor *sd,
++ const struct dsdb_class *objectclass,
++ struct dom_sid *sid, struct ldb_dn *dn,
++ bool *suppress_result)
++{
++ int ret;
++ struct parse_tree_aclread_ctx ctx = { 0 };
++ struct ldb_parse_tree *tree = ac->req->op.search.tree;
++
++ ctx.ac = ac;
++ ctx.mem_ctx = mem_ctx;
++ ctx.suppress_result = false;
++ ctx.sid = sid;
++ ctx.dn = dn;
++ ctx.sd = sd;
++ ctx.objectclass = objectclass;
++
++ /* walk the search tree, checking each attribute as we go */
++ ret = ldb_parse_tree_walk(tree, parse_tree_check_attr_access, &ctx);
++
++ /* return whether this search result should be hidden to this user */
++ *suppress_result = ctx.suppress_result;
++ return ret;
++}
++
+ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
+ {
+ struct ldb_context *ldb;
+@@ -275,6 +490,7 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
+ TALLOC_CTX *tmp_ctx;
+ uint32_t instanceType;
+ const struct dsdb_class *objectclass;
++ bool suppress_result = false;
+
+ ac = talloc_get_type(req->context, struct aclread_context);
+ ldb = ldb_module_get_ctx(ac->module);
+@@ -436,6 +652,37 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
+ goto fail;
+ }
+ }
++
++ /*
++ * check access rights for the search attributes, as well as the
++ * attribute values actually being returned
++ */
++ ret = check_search_ops_access(ac, tmp_ctx, sd, objectclass, sid,
++ msg->dn, &suppress_result);
++ if (ret != LDB_SUCCESS) {
++ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
++ "acl_read: %s check search ops %s - %s\n",
++ ldb_dn_get_linearized(msg->dn),
++ ldb_strerror(ret), ldb_errstring(ldb));
++ goto fail;
++ }
++
++ if (suppress_result) {
++
++ /*
++ * As per the above logic, we strip replPropertyMetaData
++ * out of the msg so that the dirysync module will do
++ * what is needed (return just the objectGUID if it's,
++ * deleted, or remove the object if it is not).
++ */
++ if (ac->indirsync) {
++ ldb_msg_remove_attr(msg, "replPropertyMetaData");
++ } else {
++ talloc_free(tmp_ctx);
++ return LDB_SUCCESS;
++ }
++ }
++
+ for (i=0; i < msg->num_elements; i++) {
+ if (!aclread_is_inaccessible(&msg->elements[i])) {
+ num_of_attrs++;
+--
+2.18.0
+