diff options
Diffstat (limited to 'libcli/security/tests/test_sddl_conditional_ace.c')
-rw-r--r-- | libcli/security/tests/test_sddl_conditional_ace.c | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/libcli/security/tests/test_sddl_conditional_ace.c b/libcli/security/tests/test_sddl_conditional_ace.c new file mode 100644 index 0000000..fc9281d --- /dev/null +++ b/libcli/security/tests/test_sddl_conditional_ace.c @@ -0,0 +1,1003 @@ +/* + * 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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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, /* size 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 = SECURITY_ACL_REVISION_ADS; + sec_desc_windows.sacl->revision = SECURITY_ACL_REVISION_ADS; + 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 encode into an equivalent SDDL string, + * which then parses again into the same conditional ACE. + */ + static const char *sddl[] = { + "(0>-0)", + "(0>+0)", + ("(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))))))))"), + ("(@User.a == {})"), + ("(Member_of{})"), + ("(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###))", + "(@user.x == 00)", + "(@user.x == 01)", + "(@user.x == -00)", + "(@user.x == -01)", + "(@user.x == 0x0)", + "(@user.x == 0x1)", + "(@user.x == -0x0)", + "(@user.x == -0x1)", + }; + 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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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 = true; + } + + resddl1 = sddl_from_conditional_ace(mem_ctx, s1); + if (resddl1 == NULL) { + failed = true; + debug_fail("could not re-make SDDL of %s\n", sddl[i]); + continue; + } + resddl2 = sddl_from_conditional_ace(mem_ctx, s2); + if (resddl2 == NULL) { + failed = true; + debug_fail("could not re-make SDDL of %s\n", sddl[i]); + continue; + } + if (strcmp(resddl1, resddl2) != 0) { + print_message("SDDL 2: %s\n", resddl2); + failed = true; + } + print_message("SDDL: '%s' -> '%s'\n", sddl[i], resddl1); + s3 = ace_conditions_compile_sddl(mem_ctx, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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, &e3) != 0) { + debug_fail("'%s' and '%s' compiled differently\n", sddl[i], resddl1); + failed = true; + } + } + 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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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[] = { + /* '!' is only allowed before parens or @attr */ + "(!!! !!! !!! Not_Member_of{SID(AA)}))", + /* overflowing numbers can't be sensibly interpreted */ + ("(@Device.bb == 055555624677746777766777767)"), + ("(@Device.bb == 0x624677746777766777767)"), + ("(@Device.bb == 624677746777766777767)"), + /* insufficient arguments */ + "(!)", + "(x >)", + "(> 3)", + /* keyword as local attribute name */ + "( Member_of Contains 3)", + /* no parens */ + " x < 3", + /* wants '==' */ + "( x = SID(BA))", + /* invalid SID strings */ + "( x == SID(ZZ))", + "( x == SID(S-1-))", + "( x == SID())", + /* literal on LHS */ + "(\"x\" == \"x\")", + /* odd number of digits following '#' */ + "(OctetStringType==#1#2#3##))", + /* empty expression */ + "()", + /* relational op with with complex RHS */ + "(@Device.bb == (@USER.x < 62))", + /* hex‐escapes that should be literals */ + ("(@Device.%002e == 3)"), + ("(@Device.%002f == 3)"), + ("(@Device.%003a == 3)"), + /* trailing comma in composite */ + "(Member_of{SID(AA),})", + /* missing comma between elements of a composite */ + "(Member_of{SID(AA) SID(AC)})", + /* unexpected comma in composite */ + "(Member_of{,})", + }; + 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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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_a_number_of_invalid_full_sddl_strings(void **state) +{ + /* + * These ones are complete SDDL sentences and should fail to parse, + * with specific message snippets. + */ + static struct { + const char *sddl; + const char *snippet; + ssize_t offset; + } cases[] = { + { + "O:SYG:SYD:(A;;;;ZZ)(XA;OICI;CR;;;WD;(Member_of {WD}))", + "malformed ACE with only 4 ';'", + 11 + }, + { + "O:SYG:SYD:QQ(A;;;;ZZ)(XA;OICI;CR;;;WD;(Member_of {WD}))", + "expected '[OGDS]:' section start", + 10 + } + }; + size_t i; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + bool failed_to_fail = false; + bool message_wrong = false; + enum ace_condition_flags ace_condition_flags = \ + ACE_CONDITION_FLAG_ALLOW_DEVICE; + struct dom_sid domain_sid; + string_to_sid(&domain_sid, "S-1-2-3"); + + for (i = 0; i < ARRAY_SIZE(cases); i++) { + struct security_descriptor *sd = NULL; + const char *message = NULL; + size_t message_offset; + sd = sddl_decode_err_msg(mem_ctx, + cases[i].sddl, + &domain_sid, + ace_condition_flags, + &message, + &message_offset); + if (sd != NULL) { + print_message("unexpected success: "); + debug_fail("%s\n", cases[i].sddl); + failed_to_fail = true; + } + if (cases[i].snippet != NULL) { + if (message != NULL) { + char *c = strstr(message, cases[i].snippet); + print_error_message(cases[i].sddl, + message, + message_offset); + if (c == NULL) { + message_wrong = true; + print_message("expected '%s'\n", + cases[i].snippet); + } + } else { + message_wrong = true; + print_error_message(cases[i].sddl, + "NO MESSAGE!", + message_offset); + print_message("expected '%s', got no message!\n", + cases[i].snippet); + } + } else { + print_message("no assertion about message, got '%s'\n", + message); + } + if (cases[i].offset >= 0) { + if (cases[i].offset != message_offset) { + message_wrong = true; + print_message("expected offset %zd, got %zu\n", + cases[i].offset, + message_offset); + } + } else { + print_message("no assertion about offset, got '%zu\n", + message_offset); + } + } + assert_false(failed_to_fail); + assert_false(message_wrong); +} + + +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, + ACE_CONDITION_FLAG_ALLOW_DEVICE, + 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_a_number_of_invalid_full_sddl_strings), + 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); +} |