1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00
samba-mirror/libcli/security/claims-conversions.c
Douglas Bagnall b815abe779 libcli/security: check again for NULL values
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
Autobuild-Date(master): Mon Mar 18 02:51:08 UTC 2024 on atb-devel-224
2024-03-18 02:51:08 +00:00

1217 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Unix SMB implementation.
* Utility functions for converting between claims formats.
*
* 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 "replace.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "librpc/gen_ndr/ndr_conditional_ace.h"
#include "libcli/security/claims-conversions.h"
#include "lib/util/debug.h"
#include "lib/util/stable_sort.h"
#include "librpc/gen_ndr/conditional_ace.h"
#include "librpc/gen_ndr/claims.h"
/*
* We support three formats for claims, all slightly different.
*
* 1. MS-ADTS 2.2.18.* claims sets, blobs, arrays, or whatever, which
* are used in the PAC.
*
* 2. MS-DTYP 2.4.10.1 CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1
* structures, used in security tokens and resource SACL ACEs.
*
* 3. MS-DTYP 2.4.4.17 Conditional ACE tokens.
*
* The types don't map perfectly onto each other -- in particular,
* Conditional ACEs don't have unsigned integer or boolean types, but
* do have short integer types which the other forms don't.
*
* We don't support the format used by the Win32 API function
* AddResourceAttributeAce(), which is called CLAIM_SECURITY_ATTRIBUTE_V1.
* Nobody has ever used that function in public, and the format is not used
* on the wire.
*/
static bool claim_v1_string_to_ace_string(
TALLOC_CTX *mem_ctx,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
char *s = talloc_strdup(mem_ctx,
claim->values[offset].string_value);
if (s == NULL) {
return false;
}
result->type = CONDITIONAL_ACE_TOKEN_UNICODE;
result->data.unicode.value = s;
return true;
}
static bool claim_v1_octet_string_to_ace_octet_string(
TALLOC_CTX *mem_ctx,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
DATA_BLOB *v = NULL;
DATA_BLOB w = data_blob_null;
v = claim->values[offset].octet_value;
if (v->length > CONDITIONAL_ACE_MAX_LENGTH) {
DBG_WARNING("claim has octet string of unexpected length %zu "
"(expected range 1 - %u)\n",
v->length, CONDITIONAL_ACE_MAX_LENGTH);
return false;
}
if (v->length != 0) {
w = data_blob_talloc(mem_ctx, v->data, v->length);
if (w.data == NULL) {
return false;
}
}
result->type = CONDITIONAL_ACE_TOKEN_OCTET_STRING;
result->data.bytes = w;
return true;
}
static bool blob_string_sid_to_sid(DATA_BLOB *blob,
struct dom_sid *sid)
{
/*
* Resource ACE claim SIDs are stored as SID strings in
* CLAIM_SECURITY_ATTRIBUTE_OCTET_STRING_RELATIVE blobs. These are in
* ACEs, which means we don't quite know who wrote them, and it is
* unspecified whether the blob should contain a terminating NUL byte.
* Therefore we accept either form, copying into a temporary buffer if
* there is no '\0'. Apart from this special case, we don't accept
* SIDs that are shorter than the blob.
*
* It doesn't seem like SDDL short SIDs ("WD") are accepted here. This
* isn't SDDL.
*/
bool ok;
size_t len = blob->length;
char buf[DOM_SID_STR_BUFLEN + 1]; /* 191 + 1 */
const char *end = NULL;
char *str = NULL;
if (len < 5 || len >= DOM_SID_STR_BUFLEN) {
return false;
}
if (blob->data[len - 1] == '\0') {
str = (char *)blob->data;
len--;
} else {
memcpy(buf, blob->data, len);
buf[len] = 0;
str = buf;
}
ok = dom_sid_parse_endp(str, sid, &end);
if (!ok) {
return false;
}
if (end - str != len) {
return false;
}
return true;
}
static bool claim_v1_sid_to_ace_sid(
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
/*
* In the _V1 struct, SIDs are stored as octet string blobs,
* as *SID strings*.
*
* In the conditional ACE they are stored as struct dom_sid.
*
* There are no SIDs in ADTS claims, but there can be in
* resource ACEs.
*/
DATA_BLOB *v = NULL;
bool ok;
v = claim->values[offset].sid_value;
ok = blob_string_sid_to_sid(v, &result->data.sid.sid);
if (! ok) {
DBG_WARNING("claim has invalid SID string of length %zu.\n",
v->length);
return false;
}
result->type = CONDITIONAL_ACE_TOKEN_SID;
return true;
}
static bool claim_v1_int_to_ace_int(
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
int64_t v = *claim->values[offset].int_value;
result->type = CONDITIONAL_ACE_TOKEN_INT64;
result->data.int64.base = CONDITIONAL_ACE_INT_BASE_10;
result->data.int64.value = v;
/*
* The sign flag (and the base flag above) determines how the
* ACE token will be displayed if converted to SDDL. These
* values are not likely to end up as SDDL, but we might as
* well get it right. A negative flag means it will be
* displayed with a minus sign, and a positive flag means a
* plus sign is shown. The none flag means no + or -.
*/
if (v < 0) {
result->data.int64.sign = CONDITIONAL_ACE_INT_SIGN_NEGATIVE;
} else {
result->data.int64.sign = CONDITIONAL_ACE_INT_SIGN_NONE;
}
return true;
}
static bool claim_v1_unsigned_int_to_ace_int(
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
uint64_t v = *claim->values[offset].uint_value;
if (v > INT64_MAX) {
/*
* The unsigned value can't be represented in a
* conditional ACE type.
*
* XXX or can it? does the positive flag make it
* unsigned?
*/
return false;
}
result->type = CONDITIONAL_ACE_TOKEN_INT64;
result->data.int64.base = CONDITIONAL_ACE_INT_BASE_10;
result->data.int64.sign = CONDITIONAL_ACE_INT_SIGN_POSITIVE;
result->data.int64.value = v;
return true;
}
static bool claim_v1_bool_to_ace_int(
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
uint64_t v = *claim->values[offset].uint_value;
result->type = CONDITIONAL_ACE_TOKEN_INT64;
result->data.int64.base = CONDITIONAL_ACE_INT_BASE_10;
result->data.int64.sign = CONDITIONAL_ACE_INT_SIGN_NONE;
result->data.int64.value = v ? 1 : 0;
return true;
}
static bool claim_v1_offset_to_ace_token(
TALLOC_CTX *mem_ctx,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset,
struct ace_condition_token *result)
{
/*
* A claim structure has an array of claims of a certain type,
* and this converts a single one into a conditional ACE token.
*
* For example, if offset is 3, claim->values[3] will be
* turned into *result.
*
* conditional ace token will have flags to indicate that it
* comes from a claim attribute, and whether or not that
* attribute should be compared case-sensitively (only
* affecting unicode strings).
*
* The CLAIM_SECURITY_ATTRIBUTE_CASE_SENSITIVE (from the
* claim_flags enum in security.idl) is used for both.
*/
uint8_t f = claim->flags & CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE;
result->flags = f | CONDITIONAL_ACE_FLAG_TOKEN_FROM_ATTR;
if (claim->values[offset].int_value == NULL) {
return false;
}
switch (claim->value_type) {
case CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64:
return claim_v1_int_to_ace_int(claim, offset, result);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64:
return claim_v1_unsigned_int_to_ace_int(claim, offset, result);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING:
return claim_v1_string_to_ace_string(mem_ctx, claim, offset,
result);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_SID:
return claim_v1_sid_to_ace_sid(claim, offset, result);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN:
return claim_v1_bool_to_ace_int(claim, offset, result);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING:
return claim_v1_octet_string_to_ace_octet_string(mem_ctx,
claim,
offset,
result);
default:
return false;
}
}
static bool claim_v1_copy(
TALLOC_CTX *mem_ctx,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *dest,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *src);
bool claim_v1_to_ace_composite_unchecked(
TALLOC_CTX *mem_ctx,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
struct ace_condition_token *result)
{
/*
* This converts a claim object into a conditional ACE
* composite without checking whether it is a valid and sorted
* claim. It is called in two places:
*
* 1. claim_v1_to_ace_token() below (which does do those
* checks, and is the function you want).
*
* 2. sddl_resource_attr_from_claim() in which a resource
* attribute claim needs to pass through a conditional ACE
* composite structure on its way to becoming SDDL. In that
* case we don't want to check validity.
*/
size_t i;
struct ace_condition_token *tokens = NULL;
bool ok;
tokens = talloc_array(mem_ctx,
struct ace_condition_token,
claim->value_count);
if (tokens == NULL) {
return false;
}
for (i = 0; i < claim->value_count; i++) {
ok = claim_v1_offset_to_ace_token(tokens,
claim,
i,
&tokens[i]);
if (! ok) {
TALLOC_FREE(tokens);
return false;
}
}
result->type = CONDITIONAL_ACE_TOKEN_COMPOSITE;
result->data.composite.tokens = tokens;
result->data.composite.n_members = claim->value_count;
result->flags = claim->flags & CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE;
return true;
}
bool claim_v1_to_ace_token(TALLOC_CTX *mem_ctx,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
struct ace_condition_token *result)
{
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim_copy = NULL;
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *sorted_claim = NULL;
NTSTATUS status;
bool ok;
bool case_sensitive = claim->flags & \
CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE;
if (claim->value_count < 1 ||
claim->value_count >= CONDITIONAL_ACE_MAX_TOKENS) {
DBG_WARNING("rejecting claim with %"PRIu32" tokens\n",
claim->value_count);
return false;
}
/*
* if there is one, we return a single thing of that type; if
* there are many, we return a composite.
*/
if (claim->value_count == 1) {
return claim_v1_offset_to_ace_token(mem_ctx,
claim,
0,
result);
}
if (claim->flags & CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED) {
/*
* We can avoid making a sorted copy.
*
* This is normal case for wire claims, where the
* sorting and duplicate checking happens earlier in
* token_claims_to_claims_v1().
*/
sorted_claim = claim;
} else {
/*
* This is presumably a resource attribute ACE, which
* is stored in the ACE as struct
* CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1, and we don't
* really want to mutate that copy -- even if there
* aren't currently realistic pathways that read an
* ACE, trigger this, and write it back (outside of
* tests).
*/
claim_copy = talloc(mem_ctx, struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1);
if (claim_copy == NULL) {
return false;
}
ok = claim_v1_copy(claim_copy, claim_copy, claim);
if (!ok) {
TALLOC_FREE(claim_copy);
return false;
}
status = claim_v1_check_and_sort(claim_copy, claim_copy,
case_sensitive);
if (!NT_STATUS_IS_OK(status)) {
DBG_WARNING("resource attribute claim sort failed with %s\n",
nt_errstr(status));
TALLOC_FREE(claim_copy);
return false;
}
sorted_claim = claim_copy;
}
ok = claim_v1_to_ace_composite_unchecked(mem_ctx, sorted_claim, result);
if (! ok) {
TALLOC_FREE(claim_copy);
return false;
}
/*
* The multiple values will get turned into a composite
* literal in the conditional ACE. Each element of the
* composite will have flags set by
* claim_v1_offset_to_ace_token(), but they also need to be
* set here (at least the _FROM_ATTR flag) or the child values
* will not be reached.
*/
result->flags |= (
CONDITIONAL_ACE_FLAG_TOKEN_FROM_ATTR |
CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED);
return true;
}
static bool ace_int_to_claim_v1_int(TALLOC_CTX *mem_ctx,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset)
{
int64_t *v = talloc(mem_ctx, int64_t);
if (v == NULL) {
return false;
}
*v = tok->data.int64.value;
claim->values[offset].int_value = v;
return true;
}
static bool ace_string_to_claim_v1_string(TALLOC_CTX *mem_ctx,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset)
{
const char *s = talloc_strdup(mem_ctx,
tok->data.unicode.value);
if (s == NULL) {
return false;
}
claim->values[offset].string_value = s;
return true;
}
static bool ace_sid_to_claim_v1_sid(TALLOC_CTX *mem_ctx,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset)
{
/* claim_v1 sid is an "S-1-*" string data blob, not struct dom_sid. */
char *s = NULL;
DATA_BLOB *blob = NULL;
blob = talloc(mem_ctx, DATA_BLOB);
if (blob == NULL) {
return false;
}
s = dom_sid_string(blob, &tok->data.sid.sid);
if (s == NULL) {
TALLOC_FREE(blob);
return false;
}
*blob = data_blob_string_const(s);
claim->values[offset].sid_value = blob;
return true;
}
static bool ace_octet_string_to_claim_v1_octet_string(
TALLOC_CTX *mem_ctx,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset)
{
DATA_BLOB *v = talloc(mem_ctx, DATA_BLOB);
if (v == NULL) {
return false;
}
*v = data_blob_talloc(v,
tok->data.bytes.data,
tok->data.bytes.length);
if (v->data == NULL) {
return false;
}
claim->values[offset].octet_value = v;
return true;
}
static bool ace_token_to_claim_v1_offset(TALLOC_CTX *mem_ctx,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
size_t offset)
{
/*
* A claim structure has an array of claims of a certain type,
* and this converts a single one into a conditional ACE token.
*
* For example, if offset is 3, claim->values[3] will be
* turned into *result.
*/
if (offset >= claim->value_count) {
return false;
}
switch (claim->value_type) {
case CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64:
case CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64:
return ace_int_to_claim_v1_int(mem_ctx, tok, claim, offset);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING:
return ace_string_to_claim_v1_string(mem_ctx, tok, claim, offset);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_SID:
return ace_sid_to_claim_v1_sid(mem_ctx, tok, claim, offset);
case CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING:
return ace_octet_string_to_claim_v1_octet_string(mem_ctx,
tok,
claim,
offset);
default:
/*bool unimplemented, because unreachable */
return false;
}
}
bool ace_token_to_claim_v1(TALLOC_CTX *mem_ctx,
const char *name,
const struct ace_condition_token *tok,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 **claim,
uint32_t flags)
{
size_t i;
bool ok;
bool is_comp = false;
int claim_type = -1;
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *_claim = NULL;
uint32_t value_count;
if (name == NULL || claim == NULL || tok == NULL) {
return false;
}
*claim = NULL;
if (tok->type == CONDITIONAL_ACE_TOKEN_COMPOSITE) {
is_comp = true;
/* there must be values, all of the same type */
if (tok->data.composite.n_members == 0) {
DBG_WARNING("Empty ACE composite list\n");
return false;
}
if (tok->data.composite.n_members > 1) {
for (i = 1; i < tok->data.composite.n_members; i++) {
if (tok->data.composite.tokens[i].type !=
tok->data.composite.tokens[0].type) {
DBG_WARNING(
"ACE composite list has varying "
"types (at least %u and %u)\n",
tok->data.composite.tokens[i].type,
tok->data.composite.tokens[0].type);
return false;
}
}
}
value_count = tok->data.composite.n_members;
switch (tok->data.composite.tokens[0].type) {
case CONDITIONAL_ACE_TOKEN_INT8:
case CONDITIONAL_ACE_TOKEN_INT16:
case CONDITIONAL_ACE_TOKEN_INT32:
case CONDITIONAL_ACE_TOKEN_INT64:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64;
break;
case CONDITIONAL_ACE_TOKEN_UNICODE:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING;
break;
case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING;
break;
case CONDITIONAL_ACE_TOKEN_SID:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_SID;
break;
default:
/* reject nested composites, no uint or bool. */
DBG_WARNING("ACE composite list has invalid type %u\n",
tok->data.composite.tokens[0].type);
return false;
}
} else {
value_count = 1;
switch(tok->type) {
case CONDITIONAL_ACE_TOKEN_INT8:
case CONDITIONAL_ACE_TOKEN_INT16:
case CONDITIONAL_ACE_TOKEN_INT32:
case CONDITIONAL_ACE_TOKEN_INT64:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64;
break;
case CONDITIONAL_ACE_TOKEN_UNICODE:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING;
break;
case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING;
break;
case CONDITIONAL_ACE_TOKEN_SID:
claim_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_SID;
break;
default:
/*
* no way of creating bool or uint values,
* composite is handled above.
*/
DBG_WARNING("ACE token has invalid type %u\n",
tok->data.composite.tokens[0].type);
return false;
}
}
_claim = talloc(mem_ctx, struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1);
if (_claim == NULL) {
return false;
}
_claim->value_count = value_count;
_claim->value_type = claim_type;
_claim->flags = flags;
_claim->name = talloc_strdup(mem_ctx, name);
if (_claim->name == NULL) {
TALLOC_FREE(_claim);
return false;
}
/*
* The values array is actually an array of pointers to
* values, even when the values are ints or bools.
*/
_claim->values = talloc_array(_claim, union claim_values, value_count);
if (_claim->values == NULL) {
TALLOC_FREE(_claim);
return false;
}
if (! is_comp) {
/* there is one value, not a list */
ok = ace_token_to_claim_v1_offset(_claim,
tok,
_claim,
0);
if (! ok) {
TALLOC_FREE(_claim);
return false;
}
} else {
/* a composite list of values */
for (i = 0; i < value_count; i++) {
struct ace_condition_token *t = &tok->data.composite.tokens[i];
ok = ace_token_to_claim_v1_offset(mem_ctx,
t,
_claim,
i);
if (! ok) {
TALLOC_FREE(_claim);
return false;
}
}
}
if (_claim->value_type == CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64) {
/*
* Conditional ACE tokens don't have a UINT type but
* claims do. Windows tends to use UINT types in
* claims when it can, so so do we.
*/
bool could_be_uint = true;
for (i = 0; i < value_count; i++) {
if (*_claim->values[i].int_value < 0) {
could_be_uint = false;
break;
}
}
if (could_be_uint) {
_claim->value_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64;
}
}
*claim = _claim;
return true;
}
static bool claim_v1_copy(
TALLOC_CTX *mem_ctx,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *dest,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *src)
{
DATA_BLOB blob = {0};
enum ndr_err_code ndr_err;
/*
* FIXME, could be more efficient! but copying these
* structures is fiddly, and it might be worth coming up
* with a better API for adding claims.
*/
ndr_err = ndr_push_struct_blob(
&blob, mem_ctx, src,
(ndr_push_flags_fn_t)ndr_push_CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
return false;
}
ndr_err = ndr_pull_struct_blob(
&blob, mem_ctx, dest,
(ndr_pull_flags_fn_t)ndr_pull_CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
TALLOC_FREE(blob.data);
return false;
}
TALLOC_FREE(blob.data);
return true;
}
bool add_claim_to_token(TALLOC_CTX *mem_ctx,
struct security_token *token,
const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
const char *claim_type)
{
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *tmp = NULL;
NTSTATUS status;
uint32_t *n = NULL;
bool ok;
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 **list = NULL;
if (strcmp(claim_type, "device") == 0) {
n = &token->num_device_claims;
list = &token->device_claims;
} else if (strcmp(claim_type, "local") == 0) {
n = &token->num_local_claims;
list = &token->local_claims;
} else if (strcmp(claim_type, "user") == 0) {
n = &token->num_user_claims;
list = &token->user_claims;
} else {
return false;
}
if ((*n) == UINT32_MAX) {
return false;
}
tmp = talloc_realloc(mem_ctx,
*list,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1,
(*n) + 1);
if (tmp == NULL) {
return false;
}
ok = claim_v1_copy(mem_ctx, &tmp[*n], claim);
if (! ok ) {
TALLOC_FREE(tmp);
return false;
}
status = claim_v1_check_and_sort(tmp, &tmp[*n],
claim->flags & CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE);
if (!NT_STATUS_IS_OK(status)) {
DBG_WARNING("resource attribute claim sort failed with %s\n",
nt_errstr(status));
TALLOC_FREE(tmp);
return false;
}
(*n)++;
*list = tmp;
return true;
}
static NTSTATUS claim_v1_check_and_sort_boolean(
TALLOC_CTX *mem_ctx,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim)
{
/*
* There are so few valid orders in a boolean claim that we can
* enumerate them all.
*/
switch (claim->value_count) {
case 0:
return NT_STATUS_OK;
case 1:
if (*claim->values[0].uint_value == 0 ||
*claim->values[0].uint_value == 1) {
return NT_STATUS_OK;
}
break;
case 2:
if (*claim->values[0].uint_value == 1) {
/* switch the order. */
*claim->values[0].uint_value = *claim->values[1].uint_value;
*claim->values[1].uint_value = 1;
}
if (*claim->values[0].uint_value == 0 &&
*claim->values[1].uint_value == 1) {
return NT_STATUS_OK;
}
break;
default:
/* 3 or more must have duplicates. */
break;
}
return NT_STATUS_INVALID_PARAMETER;
}
struct claim_sort_context {
uint16_t value_type;
bool failed;
bool case_sensitive;
};
static int claim_sort_cmp(const union claim_values *lhs,
const union claim_values *rhs,
struct claim_sort_context *ctx)
{
/*
* These comparisons have to match those used in
* conditional_ace.c.
*/
int cmp;
switch (ctx->value_type) {
case CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64:
case CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64:
{
/*
* We sort as signed integers, even for uint64,
* because a) we don't actually care about the true
* order, just uniqueness, and b) the conditional ACEs
* only know of signed values.
*/
int64_t a, b;
if (ctx->value_type == CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64) {
a = *lhs->int_value;
b = *rhs->int_value;
} else {
a = (int64_t)*lhs->uint_value;
b = (int64_t)*rhs->uint_value;
}
if (a < b) {
return -1;
}
if (a == b) {
return 0;
}
return 1;
}
case CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING:
{
const char *a = lhs->string_value;
const char *b = rhs->string_value;
if (ctx->case_sensitive) {
return strcmp(a, b);
}
return strcasecmp_m(a, b);
}
case CLAIM_SECURITY_ATTRIBUTE_TYPE_SID:
{
/*
* The blobs in a claim are "S-1-.." strings, not struct
* dom_sid as used in conditional ACEs, and to sort them the
* same as ACEs we need to make temporary structs.
*
* We don't accept SID claims over the wire -- these
* are resource attribute ACEs only.
*/
struct dom_sid a, b;
bool lhs_ok, rhs_ok;
lhs_ok = blob_string_sid_to_sid(lhs->sid_value, &a);
rhs_ok = blob_string_sid_to_sid(rhs->sid_value, &b);
if (!(lhs_ok && rhs_ok)) {
ctx->failed = true;
return -1;
}
cmp = dom_sid_compare(&a, &b);
return cmp;
}
case CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING:
{
const DATA_BLOB *a = lhs->octet_value;
const DATA_BLOB *b = rhs->octet_value;
return data_blob_cmp(a, b);
}
default:
ctx->failed = true;
break;
}
return -1;
}
NTSTATUS claim_v1_check_and_sort(TALLOC_CTX *mem_ctx,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim,
bool case_sensitive)
{
bool ok;
uint32_t i;
struct claim_sort_context sort_ctx = {
.failed = false,
.value_type = claim->value_type,
.case_sensitive = case_sensitive
};
/*
* It could be that the values array contains a NULL pointer, in which
* case we don't need to worry about what type it is.
*/
for (i = 0; i < claim->value_count; i++) {
if (claim->values[i].int_value == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
}
if (claim->value_type == CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN) {
NTSTATUS status = claim_v1_check_and_sort_boolean(mem_ctx, claim);
if (NT_STATUS_IS_OK(status)) {
claim->flags |= CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED;
}
return status;
}
ok = stable_sort_talloc_r(mem_ctx,
claim->values,
claim->value_count,
sizeof(union claim_values),
(samba_compare_with_context_fn_t)claim_sort_cmp,
&sort_ctx);
if (!ok) {
return NT_STATUS_NO_MEMORY;
}
if (sort_ctx.failed) {
/* this failure probably means a bad SID string */
DBG_WARNING("claim sort of %"PRIu32" members, type %"PRIu16" failed\n",
claim->value_count,
claim->value_type);
return NT_STATUS_INVALID_PARAMETER;
}
for (i = 1; i < claim->value_count; i++) {
int cmp = claim_sort_cmp(&claim->values[i - 1],
&claim->values[i],
&sort_ctx);
if (cmp == 0) {
DBG_WARNING("duplicate values in claim\n");
return NT_STATUS_INVALID_PARAMETER;
}
if (cmp > 0) {
DBG_ERR("claim sort failed!\n");
return NT_STATUS_INVALID_PARAMETER;
}
}
if (case_sensitive) {
claim->flags |= CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE;
}
claim->flags |= CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED;
return NT_STATUS_OK;
}
NTSTATUS token_claims_to_claims_v1(TALLOC_CTX *mem_ctx,
const struct CLAIMS_SET *claims_set,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 **out_claims,
uint32_t *out_n_claims)
{
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claims = NULL;
uint32_t n_claims = 0;
uint32_t expected_n_claims = 0;
uint32_t i;
NTSTATUS status;
if (out_claims == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
if (out_n_claims == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
*out_claims = NULL;
*out_n_claims = 0;
if (claims_set == NULL) {
return NT_STATUS_OK;
}
/*
* The outgoing number of claims is (at most) the sum of the
* claims_counts of each claims_array.
*/
for (i = 0; i < claims_set->claims_array_count; ++i) {
uint32_t count = claims_set->claims_arrays[i].claims_count;
expected_n_claims += count;
if (expected_n_claims < count) {
return NT_STATUS_INVALID_PARAMETER;
}
}
claims = talloc_array(mem_ctx,
struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1,
expected_n_claims);
if (claims == NULL) {
return NT_STATUS_NO_MEMORY;
}
for (i = 0; i < claims_set->claims_array_count; ++i) {
const struct CLAIMS_ARRAY *claims_array = &claims_set->claims_arrays[i];
uint32_t j;
switch (claims_array->claims_source_type) {
case CLAIMS_SOURCE_TYPE_AD:
case CLAIMS_SOURCE_TYPE_CERTIFICATE:
break;
default:
/* Ignore any claims of a type we dont recognize. */
continue;
}
for (j = 0; j < claims_array->claims_count; ++j) {
const struct CLAIM_ENTRY *claim_entry = &claims_array->claim_entries[j];
const char *name = NULL;
union claim_values *claim_values = NULL;
uint32_t n_values;
enum security_claim_value_type value_type;
switch (claim_entry->type) {
case CLAIM_TYPE_INT64:
{
const struct CLAIM_INT64 *values = &claim_entry->values.claim_int64;
uint32_t k;
int64_t *claim_values_int64 = NULL;
n_values = values->value_count;
value_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64;
claim_values = talloc_array(claims,
union claim_values,
n_values);
if (claim_values == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
claim_values_int64 = talloc_array(claims,
int64_t,
n_values);
if (claim_values_int64 == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
for (k = 0; k < n_values; ++k) {
claim_values_int64[k] = values->values[k];
claim_values[k].int_value = &claim_values_int64[k];
}
break;
}
case CLAIM_TYPE_UINT64:
case CLAIM_TYPE_BOOLEAN:
{
const struct CLAIM_UINT64 *values = &claim_entry->values.claim_uint64;
uint32_t k;
uint64_t *claim_values_uint64 = NULL;
n_values = values->value_count;
value_type = (claim_entry->type == CLAIM_TYPE_UINT64)
? CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64
: CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN;
claim_values = talloc_array(claims,
union claim_values,
n_values);
if (claim_values == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
claim_values_uint64 = talloc_array(claims,
uint64_t,
n_values);
if (claim_values_uint64 == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
for (k = 0; k < n_values; ++k) {
claim_values_uint64[k] = values->values[k];
claim_values[k].uint_value = &claim_values_uint64[k];
}
break;
}
case CLAIM_TYPE_STRING:
{
const struct CLAIM_STRING *values = &claim_entry->values.claim_string;
uint32_t k, m;
bool seen_empty = false;
n_values = values->value_count;
value_type = CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING;
claim_values = talloc_array(claims,
union claim_values,
n_values);
if (claim_values == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
m = 0;
for (k = 0; k < n_values; ++k) {
const char *string_value = NULL;
if (values->values[k] != NULL) {
string_value = talloc_strdup(claim_values, values->values[k]);
if (string_value == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
claim_values[m].string_value = string_value;
m++;
} else {
/*
* We allow one NULL string
* per claim, but not two,
* because two would be a
* duplicate, and we don't
* want those (duplicates in
* actual values are checked
* later).
*/
if (seen_empty) {
talloc_free(claims);
return NT_STATUS_INVALID_PARAMETER;
}
seen_empty = true;
}
}
n_values = m;
break;
}
default:
/*
* Other claim types are unsupported — just skip
* them.
*/
continue;
}
if (claim_entry->id != NULL) {
name = talloc_strdup(claims, claim_entry->id);
if (name == NULL) {
talloc_free(claims);
return NT_STATUS_NO_MEMORY;
}
}
claims[n_claims] = (struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1) {
.name = name,
.value_type = value_type,
.flags = 0,
.value_count = n_values,
.values = claim_values,
};
status = claim_v1_check_and_sort(claims, &claims[n_claims],
false);
if (!NT_STATUS_IS_OK(status)) {
talloc_free(claims);
DBG_WARNING("claim sort and uniqueness test failed with %s\n",
nt_errstr(status));
return status;
}
n_claims++;
}
}
*out_claims = claims;
*out_n_claims = n_claims;
return NT_STATUS_OK;
}