/*
* 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,
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--<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);
}