]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
libcli/security: test SDDL compilation in cmocka
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Wed, 12 Jul 2023 05:24:33 +0000 (17:24 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 26 Sep 2023 23:45:35 +0000 (23:45 +0000)
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
libcli/security/tests/test_sddl_conditional_ace.c [new file with mode: 0644]
libcli/security/wscript_build
selftest/tests.py

diff --git a/libcli/security/tests/test_sddl_conditional_ace.c b/libcli/security/tests/test_sddl_conditional_ace.c
new file mode 100644 (file)
index 0000000..cf487d0
--- /dev/null
@@ -0,0 +1,855 @@
+/*
+ * Unit tests for conditional ACE SDDL.
+ *
+ *  Copyright (C) Catalyst.NET Ltd 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include "cmocka.h"
+
+#include "lib/util/attr.h"
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "libcli/security/conditional_ace.h"
+#include "librpc/gen_ndr/conditional_ace.h"
+
+/*
+ * Some of the test strings break subunit, so we only print those if
+ * stdout is a terminal.
+ */
+#define debug_message(...)  do {                       \
+               if (isatty(1)) {                        \
+                       print_message(__VA_ARGS__);     \
+                               }                       \
+       } while(0)
+
+#define debug_fail(x, ...) debug_message("\033[1;31m" x "\033[0m", __VA_ARGS__)
+#define debug_ok(x, ...) debug_message("\033[1;32m" x "\033[0m", __VA_ARGS__)
+
+#define ACEINT64(x, b, s) CONDITIONAL_ACE_TOKEN_INT64,                 \
+               (x & 0xff), ((x >> 8) & 0xff), ((x >> 16) & 0xff),      \
+               ((x >> 24) & 0xff), (((uint64_t)x >> 32) & 0xff), (((uint64_t)x >> 40) & 0xff), \
+               (((uint64_t)x >> 48) & 0xff), (((uint64_t)x >> 56) & 0xff), b, s
+
+
+static void print_error_message(const char *sddl,
+                               const char *message,
+                               size_t message_offset)
+{
+       print_message("%s\n\033[1;33m %*c\033[0m\n", sddl,
+                     (int)message_offset, '^');
+       print_message("%s\n", message);
+}
+
+static void test_sddl_compile(void **state)
+{
+       /*
+        * Example codes:
+        *
+        *    CONDITIONAL_ACE_LOCAL_ATTRIBUTE,  2,0,0,0,     'x',0,
+        *    ^attr byte code                   ^              ^
+        *                       32 bit little-endian length   |
+        *                                             utf-16, little endian
+        *
+        *     CONDITIONAL_ACE_TOKEN_EQUAL
+        *     ^ op byte code with no following data
+        */
+       static const char *sddl = "(x==41 &&(x >@device.x ) )";
+       static const uint8_t ace[] = {
+               'a', 'r', 't', 'x',
+               CONDITIONAL_ACE_LOCAL_ATTRIBUTE, 2, 0, 0, 0, 'x', 0,
+               ACEINT64(41,
+                        CONDITIONAL_ACE_INT_SIGN_NONE,
+                        CONDITIONAL_ACE_INT_BASE_10),
+               CONDITIONAL_ACE_TOKEN_EQUAL,
+               CONDITIONAL_ACE_LOCAL_ATTRIBUTE, 2, 0, 0, 0, 'x', 0,
+               CONDITIONAL_ACE_DEVICE_ATTRIBUTE, 2, 0, 0, 0, 'x', 0,
+               CONDITIONAL_ACE_TOKEN_GREATER_THAN,
+               CONDITIONAL_ACE_TOKEN_AND, 0,0,0,0,
+       };
+
+       size_t i;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct ace_condition_script *s = NULL;
+       const char *message = NULL;
+       size_t message_offset;
+       bool ok;
+       DATA_BLOB compiled;
+       size_t length;
+
+       s = ace_conditions_compile_sddl(mem_ctx, sddl, &message,
+                                       &message_offset, &length);
+       if (message != NULL) {
+               print_error_message(sddl, message, message_offset);
+       }
+       if (s == NULL) {
+               debug_fail("%s\n", sddl);
+               fail();
+       }
+
+       ok = conditional_ace_encode_binary(mem_ctx, s, &compiled);
+       assert_true(ok);
+
+       assert_true(compiled.length <= ARRAY_SIZE(ace));
+       for (i = 0; i < compiled.length; i++) {
+               assert_int_equal(compiled.data[i], ace[i]);
+       }
+}
+
+static void test_sddl_compile2(void **state)
+{
+       /* this one is from Windows, not hand-calculated */
+       static const char *sddl = "(@USER.Project Any_of 1))";
+       static const uint8_t ace[] = ("artx\xf9\x0e\x00\x00\x00P\x00r"
+                                     "\x00o\x00j\x00""e\x00""c\x00t\x00"
+                                     "\x04\x01\x00\x00\x00\x00\x00\x00"
+                                     "\x00\x03\x02\x88\x00");
+       size_t i;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct ace_condition_script *s = NULL;
+       const char *message = NULL;
+       size_t message_offset;
+       bool ok;
+       DATA_BLOB compiled;
+       size_t length;
+
+       s = ace_conditions_compile_sddl(mem_ctx, sddl, &message,
+                                       &message_offset, &length);
+       if (message != NULL) {
+               print_error_message(sddl, message, message_offset);
+       }
+       if (s == NULL) {
+               debug_fail("%s\n", sddl);
+               fail();
+       }
+
+       ok = conditional_ace_encode_binary(mem_ctx, s, &compiled);
+       assert_true(ok);
+
+       assert_true(compiled.length <= ARRAY_SIZE(ace));
+       for (i = 0; i < compiled.length; i++) {
+               assert_int_equal(compiled.data[i], ace[i]);
+       }
+}
+
+static void test_full_sddl_compile(void **state)
+{
+       /*
+        * This one is from Windows, and annotated by hand.
+        *
+        * We have the bytes of a full security descriptor, in
+        * "relative" form, which is the same as the its NDR
+        * representation.
+        *
+        * *In general* we can't necessarily assert that Samba's NDR
+        * will be the same as Windows, because they could e.g. put
+        * the two ACLs in the reverse order which is also legitimate
+        * (there are hints this may vary on Windows). But in this
+        * particular case Samba and the Windows 2022 sample agree, so
+        * we can compare the bytes here.
+        *
+        * We can assert that unpacking these bytes as a security
+        * descriptor should succeed and give us exactly the same
+        * descriptor as parsing the SDDL.
+        */
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct security_descriptor sec_desc_windows = {};
+       struct security_descriptor *sec_desc_samba = NULL;
+       DATA_BLOB sd_ndr = {};
+       DATA_BLOB sd_win_push = {};
+       DATA_BLOB sd_samba_push = {};
+       bool ok;
+       enum ndr_err_code ndr_err;
+       const char *sddl = "D:(XA;;CCDCLCSWRPWP;;;MP;"\
+               "(@RESOURCE.c))S:(RA;;;;;WD;(\"colOIr\",TU,0xe,29925))";
+
+       uint8_t sd_bytes[] = {
+               1,          /*  0  version */
+               0,          /*  1  reserved */
+               20, 128,    /*  2  control */
+               0, 0, 0, 0, /*  4  owner (null relative pointer == no owner) */
+               0, 0, 0, 0, /*  8  group */
+               20, 0, 0, 0,/* 12  SACL  */
+               92, 0, 0, 0,/* 16  DACL, i.e. pointer to 92 below */
+
+               /*  20  SACL (from pointer above) */
+               4,          /* 20 revision (ADS) */
+               0,          /* 21 reserved */
+               72, 0,      /* 22 size --> takes us to 92 */
+               1, 0,       /* 24 ace count */
+               0, 0,       /* 26 reserved */
+
+               /*  now come SACL aces, of which there should be one */
+               18,         /* 28 ace type (SEC_ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE) */
+               0,          /* 29 ace flags */
+               64, 0,      /* 30 ace size (from start of ACE, again adds to ending at 92) */
+               0, 0, 0, 0, /* 32 mask */
+
+               /*  here's the ACE SID */
+               1,                 /* 36 revision */
+               1,                 /* 37 sub-auth count */
+               0, 0, 0, 0, 0, 1,  /* 38 big endian ident auth */
+               0, 0, 0, 0,        /* 44 the sub-auth  (so SID is S-1-1-0 (everyone), mandatory with RA ace) */
+
+               /*  here starts the actual claim, at 48 */
+               20, 0, 0, 0,       /* 48 pointer to name (relative to claim, at 68) */
+               2, 0,              /* 52 value type (uint64) */
+               0, 0,              /* 54 reserved */
+               14, 0, 0, 0,       /* 56 flags (case-sensitive|deny-only|disabled-by-default -- the "0xe" in the SDDL) */
+               1, 0, 0, 0,        /* 60 value count */
+               34, 0, 0, 0,       /* 64 array of pointers, 1-long, points to 48 + 34 == 82 */
+                                  /* 68 utf-16 letters "colOIr\0", indicated by name pointer at 48 */
+               'c', 0,
+               'o', 0,
+               'l', 0,
+               'O', 0,            /* unlike conditional ACE strings, this is nul-terminated. */
+               'I', 0,            /*   where does the next thing start:        */
+               'r', 0,            /*   6 letters + '\0' * 2 = 14. 68 + 14 = 82 */
+               0, 0,
+                                  /* 82 is the value pointed to at 64 above (LE uint64) */
+               229, 116, 0, 0, 0, 0, 0, 0, /* this equals 229 + 116 * 256 == 29925, as we see in the SDDL. */
+
+               /*  88 the claim has ended. the ace has NEARLY ended, but we need to round up: */
+
+               0, 0,              /* 90 two bytes of padding to get to a multiple of 4. */
+               /* The ace and SACL have ended */
+
+               /*  92 the DACL starts. */
+               2,              /* 92 version (NT) */
+               0,              /* 93 reserved  */
+               40, 0,          /* 94 size */
+               1, 0,           /* 96 ace count */
+               0, 0,           /* 98 reserved */
+               /*  100 the DACL aces start */
+               9,              /* 100  ace type (SEC_ACE_TYPE_ACCESS_ALLOWED_CALLBACK) */
+               0,              /* 101  flags */
+               32, 0,          /* 102  ace size (ending at 132) */
+               63, 0, 0, 0,    /* 104  mask (let's assume CCDCLCSWRPWP as in sddl, not checked, but it's the right number of bits) */
+               /*  108 the ACE sid */
+               1,              /* 108 version */
+               1,              /* 109 sub-auths */
+               0, 0, 0, 0, 0, 16,/* 110 bigendian 16 identauth */
+               0, 33, 0, 0,    /* 116 sub-auth 1, 33 << 8 == 8448;  "S-1-16-8448" == "ML_MEDIUM_PLUS" == "MP" */
+               /*  120 here starts the callback */
+               97, 114, 116, 120, /* 120 'artx' */
+               250,              /* 124 0xfa CONDITIONAL_ACE_RESOURCE_ATTRIBUTE token */
+               2, 0, 0, 0,       /* 125 length 2 (bytes) */
+               'c', 0,            /* 129 utf-16 "c" -- NOT nul-terminated */
+               0                 /* 131 padding to bring length to a multiple of 4 (132) */
+       };
+       sd_ndr.length = 132;
+       sd_ndr.data = sd_bytes;
+
+       sec_desc_samba = sddl_decode(mem_ctx, sddl, NULL);
+       assert_non_null(sec_desc_samba);
+       ndr_err = ndr_pull_struct_blob(
+               &sd_ndr, mem_ctx, &sec_desc_windows,
+               (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+       /*
+        * look, we munge the DACL version byte before comparing,
+        * because Samba currently always does version 4.
+        */
+       sec_desc_windows.dacl->revision = SECURITY_ACL_REVISION_ADS;
+       sd_bytes[92] = SECURITY_ACL_REVISION_ADS;
+
+       /* push the structures back into blobs for 3-way comparisons. */
+       ndr_err = ndr_push_struct_blob(
+               &sd_win_push, mem_ctx,
+               &sec_desc_windows,
+               (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+       ndr_err = ndr_push_struct_blob(
+               &sd_samba_push, mem_ctx,
+               sec_desc_samba,
+               (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+       assert_int_equal(sd_samba_push.length, sd_win_push.length);
+       assert_int_equal(sd_samba_push.length, sd_ndr.length);
+       assert_memory_equal(sd_samba_push.data,
+                           sd_win_push.data,
+                           sd_win_push.length);
+       assert_memory_equal(sd_win_push.data,
+                           sd_ndr.data,
+                           sd_ndr.length);
+
+       ok = security_descriptor_equal(sec_desc_samba, &sec_desc_windows);
+       assert_true(ok);
+       talloc_free(mem_ctx);
+}
+
+
+static void debug_conditional_ace_stderr(TALLOC_CTX *mem_ctx,
+                                        struct ace_condition_script *program)
+{
+       char * debug_string = debug_conditional_ace(mem_ctx, program);
+
+       if (debug_string != NULL) {
+               fputs(debug_string, stderr);
+               TALLOC_FREE(debug_string);
+       } else {
+               print_message("failed to debug!\n");
+       }
+}
+
+
+static void test_full_sddl_ra_encode(void **state)
+{
+       /*
+        * This is an example from Windows that Samba once had trouble
+        * with.
+        */
+       bool ok;
+       enum ndr_err_code ndr_err;
+       char *sddl = NULL;
+       struct dom_sid domain_sid;
+       uint8_t win_bytes[] = {
+               0x01, 0x00, 0x14, 0x80, /* descriptor header */
+               0x00, 0x00, 0x00, 0x00, /* NULL owner pointer */
+               0x00, 0x00, 0x00, 0x00, /* NULL group pointer */
+               0x14, 0x00, 0x00, 0x00, /* SACL at 0x14 (20) */
+               0x58, 0x01, 0x00, 0x00, /* DACL at 0x158 (344) */
+               /* SACL starts here (20) */
+               0x02, 0x00, /* rev 2, NT */
+               0x44, 0x01, /* size 0x0144 (324) -- ends at 344 */
+               0x01, 0x00, /* ace count */
+               0x00, 0x00, /* reserved */
+               /* ace starts here, 28 */
+               0x12, 0x00, /* ace type, flags: 0x12(18) is resource attribute  */
+               0x3c, 0x01, /* ACE size 0x13c == 316, from ACE start, end at 344 */
+               0x00, 0x00, 0x00, 0x00, /*ACE mask */
+               0x01, 0x01,  /* SID S-1-<identauth>-<1 subauth>) */
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x01, /* -1- indent auth */
+               0x00, 0x00, 0x00, 0x00,  /* -0  -> S-1-1-0, world */
+               /* claim starts here, 48 */
+               0x28, 0x00, 0x00, 0x00, /* pointer to name 40 (from claim start 48) = 88 */
+               0x10, 0x00,             /* type octet string */
+               0x00, 0x00,             /* empty */
+               0x00, 0x00, 0x00, 0x00, /* zero flags */
+               0x06, 0x00, 0x00, 0x00, /* value count */
+               /* array of 6 value pointers (at claim + 16, 64) */
+               0xf2, 0x00, 0x00, 0x00,  /* value 0xf2 = 242 from claim (48) == 290 */
+               0xf8, 0x00, 0x00, 0x00,  /* 0xf8, 248 */
+               0x0d, 0x01, 0x00, 0x00,  /* 0x10d, 269 */
+               0x14, 0x01, 0x00, 0x00,  /* 0x114, 276 */
+               0x1a, 0x01, 0x00, 0x00,  /* 0x11a, 282 */
+               0x21, 0x01, 0x00, 0x00,  /* 0x121, 289 */
+               /* here's the name, at 88 */
+               'c', 0x00,
+               'o', 0x00,
+               'l', 0x00,
+               'O', 0x00,
+               'I', 0x00,
+               'r', 0x00,  /* the following lines are all \x16 */
+               /* 100 */
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               /* 150 */
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               /* 200 */
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               /* 250 */
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               /* 280 */
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00,  /* 286 */
+               'r', 0x00,
+               0x00, 0x00,   /* name is nul-terminated */
+               /* 290, first octet string blob */
+               0x02, 0x00, 0x00, 0x00, /* length 2 */
+               0x00, 0x77,             /* 2 blob bytes */
+               /* second blob @ 48 + 248 == 296 */
+               0x11, 0x00, 0x00, 0x00, /* length 0x11 = 17 */
+               0x00, 0x77, 0x77, 0x71, 0x83, 0x68, 0x96, 0x62, 0x95, 0x93,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
+               /* third blob at 269 + 48 == 317 */
+               0x03, 0x00, 0x00, 0x00,
+               0x00, 0x77, 0x77,
+               /* fourth blob, 276 + 48 == 324 */
+               0x02, 0x00, 0x00, 0x00,
+               0x00, 0x77,
+               /* fifth blob, 282 + 48 == 330 */
+               0x03, 0x00, 0x00, 0x00,
+               0x00, 0x77, 0x77,
+               /* last blob 289 + 48 == 337 */
+               0x03, 0x00, 0x00, 0x00,
+               0x00, 0x77, 0x77,
+               /* claim ends */
+               /* 344 DACL starts */
+               0x02, 0x00, /* rev 2 (NT) */
+               0x28, 0x00, /* size 40, ending at 384 */
+               0x01, 0x00, /* ace count */
+               0x00, 0x00,
+               /* ACE starts here, 352 */
+               0x09, 0x00, /* type 9, access allowed callback */
+               0x20, 0x00, /* swize 32 */
+               0x3f, 0x00, 0x00, 0x00, /*mask */
+               0x01, 0x01, /* S-1-... (1 subauth) */
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x10, /*...-16-...*/
+               0x00, 0x21, 0x00, 0x00, /* -5356. S-1-16-5376 */
+               'a', 'r', 't', 'x',
+               0xfa, /* resource attr */
+               0x02, 0x00, 0x00, 0x00, /*name is 2 bytes long (i.e. 1 UTF-16) */
+               'c', 0x00, /* name is "c" */
+               /* here we're at 383, but need to round to a multiple of 4 with zeros: */
+               0x00
+       };
+       DATA_BLOB win_blob = {
+               .data = win_bytes,
+               .length = sizeof(win_bytes)
+       };
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct security_descriptor sec_desc_windows = {};
+       struct security_descriptor *sec_desc_samba = NULL;
+
+       ndr_err = ndr_pull_struct_blob(
+               &win_blob, mem_ctx, &sec_desc_windows,
+               (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+       string_to_sid(&domain_sid, "S-1-2-3");
+       sddl = sddl_encode(mem_ctx, &sec_desc_windows, &domain_sid);
+       assert_non_null(sddl);
+       sec_desc_samba = sddl_decode(mem_ctx, sddl, &domain_sid);
+
+       /* hack the acl revision numbers */
+       sec_desc_windows.dacl->revision = 4;
+       sec_desc_windows.sacl->revision = 4;
+       ok = security_descriptor_equal(sec_desc_samba, &sec_desc_windows);
+       assert_true(ok);
+       talloc_free(mem_ctx);
+}
+
+
+static void test_full_sddl_ra_escapes(void **state)
+{
+       /*
+        * This is the security descriptor described in
+        * test_full_sddl_ra_encode(), with SDDL.
+        */
+       enum ndr_err_code ndr_err;
+       const char *sddl = (
+               "D:(XA;;CCDCLCSWRPWP;;;MP;(@RESOURCE.c))S:(RA;;;;;WD;(\""
+               "colOIr%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016%0016"
+               "%0016%0016%0016%0016%0016%0016r\","
+               "TX,0x0,"
+               "#0077,#00,#0077,#00,#0077,#00,#00,#00,#0077,#00,#0077,"
+               "#00,#0077,#007777,#007777,#0077,#007777,#0077,#007777,"
+               "#007770,#0077,#00,#0077,#00,#00,#00,#0077,#00,#0077,#00,"
+               "#0077,#007777,#007777,#0077,#007777,#0077,#007777,#007777))");
+       uint8_t win_bytes[] = {
+               0x01, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xb0, 0x02, 0x00, 0x00,
+               0x02, 0x00, 0x9c, 0x02, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00,
+               0x94, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00,
+               0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x26, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x00, 0x00, 0xa4, 0x01,
+               0x00, 0x00, 0xa9, 0x01, 0x00, 0x00, 0xaf, 0x01, 0x00, 0x00,
+               0xb4, 0x01, 0x00, 0x00, 0xba, 0x01, 0x00, 0x00, 0xbf, 0x01,
+               0x00, 0x00, 0xc4, 0x01, 0x00, 0x00, 0xc9, 0x01, 0x00, 0x00,
+               0xcf, 0x01, 0x00, 0x00, 0xd4, 0x01, 0x00, 0x00, 0xda, 0x01,
+               0x00, 0x00, 0xdf, 0x01, 0x00, 0x00, 0xe5, 0x01, 0x00, 0x00,
+               0xec, 0x01, 0x00, 0x00, 0xf3, 0x01, 0x00, 0x00, 0xf9, 0x01,
+               0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00,
+               0x0d, 0x02, 0x00, 0x00, 0x14, 0x02, 0x00, 0x00, 0x1a, 0x02,
+               0x00, 0x00, 0x1f, 0x02, 0x00, 0x00, 0x25, 0x02, 0x00, 0x00,
+               0x2a, 0x02, 0x00, 0x00, 0x2f, 0x02, 0x00, 0x00, 0x34, 0x02,
+               0x00, 0x00, 0x3a, 0x02, 0x00, 0x00, 0x3f, 0x02, 0x00, 0x00,
+               0x45, 0x02, 0x00, 0x00, 0x4a, 0x02, 0x00, 0x00, 0x50, 0x02,
+               0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0x5e, 0x02, 0x00, 0x00,
+               0x64, 0x02, 0x00, 0x00, 0x6b, 0x02, 0x00, 0x00, 0x71, 0x02,
+               0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+               0x6c, 0x00, 0x4f, 0x00, 0x49, 0x00, 0x72, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00,
+               0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x72, 0x00,
+               0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00,
+               0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01,
+               0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77,
+               0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+               0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+               0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+               0x00, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+               0x00, 0x00, 0x77, 0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77,
+               0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x02, 0x00, 0x00,
+               0x00, 0x00, 0x77, 0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77,
+               0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x03, 0x00, 0x00, 0x00,
+               0x00, 0x77, 0x77, 0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x70,
+               0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x00,
+               0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00,
+               0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+               0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00,
+               0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01,
+               0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77,
+               0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x03, 0x00, 0x00,
+               0x00, 0x00, 0x77, 0x77, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77,
+               0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x02, 0x00, 0x00,
+               0x00, 0x00, 0x77, 0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77,
+               0x03, 0x00, 0x00, 0x00, 0x00, 0x77, 0x77, 0x00, 0x02, 0x00,
+               0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x20, 0x00,
+               0x3f, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x10, 0x00, 0x21, 0x00, 0x00, 0x61, 0x72, 0x74, 0x78,
+               0xfa, 0x02, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00};
+       DATA_BLOB win_blob = {
+               .data = win_bytes,
+               .length = sizeof(win_bytes)
+       };
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct security_descriptor sec_desc_windows = {};
+       struct security_descriptor *sec_desc_samba = sddl_decode(mem_ctx, sddl,
+                                                                NULL);
+       assert_non_null(sec_desc_samba);
+       ndr_err = ndr_pull_struct_blob(
+               &win_blob, mem_ctx, &sec_desc_windows,
+               (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+       assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+}
+
+static void test_round_trips(void **state)
+{
+       /*
+        * These expressions should parse into proper conditional
+        * ACEs, which then decode into the same string.
+        */
+       static const char *sddl[] = {
+               ("(Member_of{SID(AA)})"),
+               ("(a Contains @USER.b == @device.c)"),
+               ("(a == @user.b == @resource.c)"),
+               ("(@Device.bb <= -00624677746777766777767)"),
+               ("(@Device.bb == 0624677746777766777767)"),
+               ("(@Device.%025cɜ == 3)"),
+               ("(17pq == 3||2a==@USER.7)"),
+               ("(x==1 && x >= 2 && @User.Title == @User.shoes || "
+                "Member_of{SID(CD)} && !(Member_of_Any{{ 3 }}) || "
+                "Device_Member_of{SID(BA), {{7, 1}, 3}} "
+                "|| Exists hooly)"),
+               ("(!(!(!(!(!((!(x==1))))))))"),
+               ("(Member_of {SID(S-1-33-5), "
+                "SID(BO)} && @Device.Bitlocker)"),
+               "(@USER.ad://ext/AuthenticationSilo == \"siloname\")",
+               "(@User.Division==\"Finance\" || @User.Division ==\"Sales\")",
+               "(@User.Title == @User.Title)",
+               "(@User.Title == \"PM\")",
+               "(OctetStringType==#01020300)",
+               "(@User.Project Any_of @Resource.Project)",
+               "(@user.x==1 &&(@user.x >@user.x ) )",
+               "(x==1) ",
+               "( x Contains 3)",
+               "( x < 3)",
+               "(x Any_of 3)",
+               "( x == SID(BA))",
+               "((x) == SID(BA))",
+               "(OctetStringType==#1#2#3###))",
+       };
+       size_t i, length;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       bool failed = false;
+       bool ok;
+       for (i = 0; i < ARRAY_SIZE(sddl); i++) {
+               struct ace_condition_script *s1 = NULL;
+               struct ace_condition_script *s2 = NULL;
+               struct ace_condition_script *s3 = NULL;
+               const char *message = NULL;
+               size_t message_offset;
+               const char *resddl1 = NULL;
+               const char *resddl2 = NULL;
+               DATA_BLOB e1, e2, e3;
+               fputs("=======================\n", stderr);
+               s1 = ace_conditions_compile_sddl(mem_ctx,
+                                                sddl[i],
+                                                &message,
+                                                &message_offset,
+                                                &length);
+               if (s1 == NULL) {
+                       debug_fail("%s\n", sddl[i]);
+                       failed = true;
+                       print_error_message(sddl[i], message, message_offset);
+                       continue;
+               }
+               if (false) {
+                       debug_conditional_ace_stderr(mem_ctx, s1);
+               }
+               ok = conditional_ace_encode_binary(mem_ctx, s1, &e1);
+               if (! ok) {
+                       failed = true;
+                       debug_fail("%s could not encode\n", sddl[i]);
+                       continue;
+               }
+
+               s2 = parse_conditional_ace(mem_ctx, e1);
+               if (s2 == NULL) {
+                       debug_fail("%s failed to decode ace\n", sddl[i]);
+                       failed = true;
+                       continue;
+               }
+
+               ok = conditional_ace_encode_binary(mem_ctx, s2, &e2);
+               if (! ok) {
+                       failed = true;
+                       debug_fail("%s could not re-encode\n", sddl[i]);
+                       continue;
+               }
+               if (data_blob_cmp(&e1, &e2) != 0) {
+                       failed = failed || ok;
+               }
+
+               resddl1 = sddl_from_conditional_ace(mem_ctx, s1);
+               resddl2 = sddl_from_conditional_ace(mem_ctx, s2);
+               if (strcmp(resddl1, resddl2) != 0) {
+                       print_message("SDDL 2: %s\n", resddl2);
+                       failed = failed || ok;
+               }
+               print_message("SDDL: %s\n", resddl1);
+               s3 = ace_conditions_compile_sddl(mem_ctx,
+                                                resddl1,
+                                                &message,
+                                                &message_offset,
+                                                &length);
+               if (s3 == NULL) {
+                       debug_fail("resddl: %s\n", resddl1);
+                       failed = true;
+                       print_error_message(resddl1, message, message_offset);
+                       continue;
+               }
+               ok = conditional_ace_encode_binary(mem_ctx, s3, &e3);
+               if (! ok) {
+                       failed = true;
+                       debug_fail("%s could not encode\n", resddl1);
+                       continue;
+               }
+               if (data_blob_cmp(&e1, &e2) != 0) {
+                       debug_fail("'%s' compiled differently\n", resddl1);
+                       failed = failed || ok;
+               }
+       }
+       assert_false(failed);
+}
+
+static void test_a_number_of_valid_strings(void **state)
+{
+       /*
+        * These expressions should parse into proper conditional ACEs.
+        */
+       static const char *sddl[] = {
+               "(@User.TEETH == \"5\")",
+               "(x==1) ",
+               "( x Contains 3)",
+               "( x < 3)",
+               "(x Any_of 3)",
+               "( x == SID(BA))",
+               "(x ANY_Of 3)",
+               "((x) == SID(BA))",
+               "(x==1 && x >= 2)", /* logical consistency not required */
+       };
+       size_t i, length;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       bool failed = false;
+       for (i = 0; i < ARRAY_SIZE(sddl); i++) {
+               struct ace_condition_script *s = NULL;
+               const char *message = NULL;
+               size_t message_offset;
+
+               s = ace_conditions_compile_sddl(mem_ctx,
+                                               sddl[i],
+                                               &message,
+                                               &message_offset,
+                                               &length);
+               if (s == NULL) {
+                       debug_fail("%s\n", sddl[i]);
+                       failed = true;
+               } else if (length != strlen(sddl[i])) {
+                       debug_fail("%s failed to consume whole string\n",
+                                  sddl[i]);
+                       failed = true;
+               }
+               if (message != NULL) {
+                       print_error_message(sddl[i], message, message_offset);
+               } else if (s == NULL) {
+                       print_message("failed without message\n");
+               }
+       }
+       assert_false(failed);
+}
+
+
+static void test_a_number_of_invalid_strings(void **state)
+{
+       /*
+        * These expressions should fail to parse.
+        */
+       static const char *sddl[] = {
+               "(!!! !!!  !!! Not_Member_of{SID(AA)}))",
+               ("(@Device.bb == 055555624677746777766777767)"),
+               ("(@Device.bb == 0x624677746777766777767)"),
+               ("(@Device.bb == 624677746777766777767)"),
+               "(!)",
+               "(x >)",
+               "( Member_of Contains 3)",
+               " x < 3",
+               "( x = SID(BA))",
+               "( x == SID(ZZ))",
+               "( x == SID())",
+               "(\"x\" == \"x\")",
+               "(OctetStringType==#1#2#3##))",
+       };
+       size_t i, length;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       bool failed_to_fail = false;
+       for (i = 0; i < ARRAY_SIZE(sddl); i++) {
+               struct ace_condition_script *s = NULL;
+               const char *message = NULL;
+               size_t message_offset;
+               s = ace_conditions_compile_sddl(mem_ctx,
+                                               sddl[i],
+                                               &message,
+                                               &message_offset,
+                                               &length);
+               if (s != NULL) {
+                       print_message("unexpected success: ");
+                       debug_fail("%s\n", sddl[i]);
+                       failed_to_fail = true;
+               }
+               if (message != NULL) {
+                       print_error_message(sddl[i], message, message_offset);
+               } else if (s == NULL) {
+                       print_message("failed without message\n");
+               }
+       }
+       assert_false(failed_to_fail);
+}
+
+
+static void test_valid_strings_with_trailing_crap(void **state)
+{
+       /*
+        * These expressions should parse even though they have
+        * trailing bytes that look bad.
+        *
+        *  ace_conditions_compile_sddl() will return when it has
+        *  found a complete expression, and tell us how much it used.
+        */
+       static struct {
+               const char *sddl;
+               size_t length;
+       } pairs[] = {
+               {"(x==1 &&(x < 5 )) )", 18},
+               {"(x==1) &&", 7},
+               {"(x)) ", 3},
+       };
+       size_t i, length;
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       bool failed = false;
+       for (i = 0; i < ARRAY_SIZE(pairs); i++) {
+               struct ace_condition_script *s = NULL;
+               const char *message = NULL;
+               size_t message_offset;
+               s = ace_conditions_compile_sddl(mem_ctx,
+                                               pairs[i].sddl,
+                                               &message,
+                                               &message_offset,
+                                               &length);
+
+               if (s == NULL) {
+                       debug_fail("%s\n", pairs[i].sddl);
+                       failed = true;
+               } else if (pairs[i].length == length) {
+                       debug_ok("%s\n", pairs[i].sddl);
+               } else {
+                       debug_fail("expected to consume %zu bytes, actual %zu\n",
+                                  pairs[i].length, length);
+                       failed = true;
+               }
+               if (message != NULL) {
+                       print_error_message(pairs[i].sddl, message, message_offset);
+               } else if (s == NULL) {
+                       print_message("failed without message\n");
+               }
+       }
+       assert_false(failed);
+}
+
+
+int main(_UNUSED_ int argc, _UNUSED_ const char **argv)
+{
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test(test_full_sddl_ra_encode),
+               cmocka_unit_test(test_full_sddl_ra_escapes),
+               cmocka_unit_test(test_full_sddl_compile),
+               cmocka_unit_test(test_round_trips),
+               cmocka_unit_test(test_a_number_of_invalid_strings),
+               cmocka_unit_test(test_a_number_of_valid_strings),
+               cmocka_unit_test(test_valid_strings_with_trailing_crap),
+               cmocka_unit_test(test_sddl_compile),
+               cmocka_unit_test(test_sddl_compile2),
+       };
+       if (!isatty(1)) {
+               cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+       }
+       return cmocka_run_group_tests(tests, NULL, NULL);
+}
index 9f2755ae0a0500ed7e14e4f00ec4ec859aab7e24..f8f10e787c54ff261eb7960c92d4c438953c2eb6 100644 (file)
@@ -18,3 +18,17 @@ bld.SAMBA_PYTHON('pysecurity',
                  deps='samba-security %s' % pytalloc_util,
                  realname='samba/security.so'
                  )
+
+bld.SAMBA_BINARY(
+    'test_sddl_conditional_ace',
+    source='tests/test_sddl_conditional_ace.c',
+    deps='''
+        cmocka
+        talloc
+        samba-util
+        asn1util
+        NDR_SECURITY
+        samba-security
+    ''',
+    for_selftest=True
+)
index 2cafe2faa4e90fbb429c586662c2b4daaf941e5f..68271e70bc13da8ae43b833de3b126fd3293563d 100644 (file)
@@ -485,3 +485,6 @@ plantestsuite("samba.unittests.compression.lzxpress_huffman", "none",
 plantestsuite("samba.unittests.compression.lzxpress_plain", "none",
               [os.path.join(bindir(),
                             "default/lib/compression/test_lzxpress_plain")])
+
+plantestsuite("samba.unittests.sddl_conditional_ace", "none",
+              [os.path.join(bindir(), "test_sddl_conditional_ace")])