From: Douglas Bagnall Date: Wed, 12 Jul 2023 05:24:33 +0000 (+1200) Subject: libcli/security: test SDDL compilation in cmocka X-Git-Tag: tevent-0.16.0~466 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e4865a3ba156124c111956b94abbc05d6da41f4c;p=thirdparty%2Fsamba.git libcli/security: test SDDL compilation in cmocka Signed-off-by: Douglas Bagnall Reviewed-by: Andrew Bartlett --- 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 00000000000..cf487d09f25 --- /dev/null +++ b/libcli/security/tests/test_sddl_conditional_ace.c @@ -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 . + * + */ + +#include +#include +#include +#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--<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); +} diff --git a/libcli/security/wscript_build b/libcli/security/wscript_build index 9f2755ae0a0..f8f10e787c5 100644 --- a/libcli/security/wscript_build +++ b/libcli/security/wscript_build @@ -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 +) diff --git a/selftest/tests.py b/selftest/tests.py index 2cafe2faa4e..68271e70bc1 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -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")])