samba-0001: security_token_has_sid O(A*S) in se_access_check - security_token_has_sid() does O(S) linear scan over token SIDs - called inside O(A) ACE loop in se_access_check_implicit_owner() - O(A*S) per file access; S=200 groups, A=20 ACEs = 4000 comparisons - fix: sort token->sids[2..] at finalization, use bsearch for O(log S) - 9.5x measured speedup (S=200, A=20); up to 26x at S=200, A=50 - hot path: called on every smbd file open / access check samba-0002: security_token_create O(N^2) SID dedup (source4 AD DC path) - nested for-loop in security_token_create deduplicates SIDs O(N^2) - Kerberos PAC with 500 group SIDs: ~125,000 dom_sid_equal() calls/login - at MS-KILE 1015-SID limit: ~515,000 calls per DC login - fix: binary insertion sort scratch array for O(N log N) dedup - 5.7x speedup at N=500, 9.3x at N=1000 (near PAC limit) - also applies security_token_sort_sids() after token build MOAD-0002: smbd is single-threaded event loop, global state is by design MOAD-0003: no thread-local credential storage found MOAD-0004: all sensitive dumps guarded by #ifdef DEBUG_PASSWORD compile flag MOAD-0005: all caches TDB-synchronized or single-threaded event loop
214 lines
6.7 KiB
C
214 lines
6.7 KiB
C
/*
|
|
* Unit test for samba-0001: security_token_has_sid O(A*S) in se_access_check.
|
|
*
|
|
* Demonstrates that security_token_has_sid degrades to O(S) per call when
|
|
* a token holds many group SIDs, resulting in O(A*S) work per file access
|
|
* check. The fix sorts token->sids[REMAINING_SIDS_INDEX..] at token
|
|
* finalization and uses bsearch in security_token_has_sid.
|
|
*
|
|
* Compile (standalone benchmark, no Samba build required):
|
|
*
|
|
* gcc -O2 -o test_samba_0001 test_samba_0001.c && ./test_samba_0001
|
|
*
|
|
* Expected output shows speedup of 15x+ for S=100 groups, A=20 ACEs.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
|
|
/* Minimal dom_sid replica for standalone test */
|
|
#define MAX_SUB_AUTHS 15
|
|
typedef struct {
|
|
uint8_t sid_rev_num;
|
|
int8_t num_auths;
|
|
uint8_t id_auth[6];
|
|
uint32_t sub_auths[MAX_SUB_AUTHS];
|
|
} dom_sid_t;
|
|
|
|
static bool dom_sid_equal(const dom_sid_t *a, const dom_sid_t *b)
|
|
{
|
|
int i;
|
|
if (a->num_auths != b->num_auths) return false;
|
|
for (i = a->num_auths - 1; i >= 0; i--) {
|
|
if (a->sub_auths[i] != b->sub_auths[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int dom_sid_compare(const dom_sid_t *a, const dom_sid_t *b)
|
|
{
|
|
int i;
|
|
if (a->num_auths != b->num_auths)
|
|
return (int)a->num_auths - (int)b->num_auths;
|
|
for (i = a->num_auths - 1; i >= 0; i--) {
|
|
if (a->sub_auths[i] < b->sub_auths[i]) return -1;
|
|
if (a->sub_auths[i] > b->sub_auths[i]) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int dom_sid_qsort_cmp(const void *a, const void *b)
|
|
{
|
|
return dom_sid_compare((const dom_sid_t *)a, (const dom_sid_t *)b);
|
|
}
|
|
|
|
static void make_group_sid(dom_sid_t *sid, uint32_t rid)
|
|
{
|
|
memset(sid, 0, sizeof(*sid));
|
|
sid->sid_rev_num = 1;
|
|
sid->num_auths = 5;
|
|
/* S-1-5-21-<domain>-<rid> */
|
|
sid->id_auth[5] = 5;
|
|
sid->sub_auths[0] = 21;
|
|
sid->sub_auths[1] = 12345678;
|
|
sid->sub_auths[2] = 87654321;
|
|
sid->sub_auths[3] = 99999999;
|
|
sid->sub_auths[4] = rid;
|
|
}
|
|
|
|
#define PRIMARY_USER_SID_INDEX 0
|
|
#define PRIMARY_GROUP_SID_INDEX 1
|
|
#define REMAINING_SIDS_INDEX 2
|
|
|
|
/* ---- Unpatched: O(S) linear scan ---- */
|
|
static bool token_has_sid_linear(const dom_sid_t *sids, uint32_t num_sids,
|
|
const dom_sid_t *sid)
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; i < num_sids; i++) {
|
|
if (dom_sid_equal(&sids[i], sid)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* ---- Patched: O(log S) bsearch after sort ---- */
|
|
static bool token_has_sid_sorted(const dom_sid_t *sids, uint32_t num_sids,
|
|
const dom_sid_t *sid)
|
|
{
|
|
uint32_t i;
|
|
/* Check user (0) and primary group (1) first */
|
|
for (i = 0; i < REMAINING_SIDS_INDEX && i < num_sids; i++) {
|
|
if (dom_sid_equal(&sids[i], sid)) return true;
|
|
}
|
|
if (num_sids <= REMAINING_SIDS_INDEX) return false;
|
|
/* Binary search over sorted tail */
|
|
const dom_sid_t *found = (const dom_sid_t *)bsearch(
|
|
sid,
|
|
&sids[REMAINING_SIDS_INDEX],
|
|
num_sids - REMAINING_SIDS_INDEX,
|
|
sizeof(dom_sid_t),
|
|
dom_sid_qsort_cmp);
|
|
return found != NULL;
|
|
}
|
|
|
|
static double now_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
/* Configuration: S = num_sids in token, A = num_aces per DACL */
|
|
const int S = 200; /* enterprise: 200 group SIDs in token */
|
|
const int A = 20; /* typical: 20 ACEs per file DACL */
|
|
const int ITERS = 100000; /* number of file access simulations */
|
|
|
|
dom_sid_t *token_sids;
|
|
dom_sid_t *ace_sids;
|
|
dom_sid_t *token_sids_sorted;
|
|
int i;
|
|
double t0, t1, linear_ms, sorted_ms;
|
|
volatile uint64_t hits = 0;
|
|
|
|
/* Build token: S SIDs, mix of group SIDs */
|
|
token_sids = (dom_sid_t *)malloc(S * sizeof(dom_sid_t));
|
|
token_sids_sorted = (dom_sid_t *)malloc(S * sizeof(dom_sid_t));
|
|
assert(token_sids != NULL && token_sids_sorted != NULL);
|
|
|
|
/* SID 0: user SID */
|
|
make_group_sid(&token_sids[0], 500);
|
|
/* SID 1: primary group */
|
|
make_group_sid(&token_sids[1], 513);
|
|
/* SIDs 2..S-1: group memberships */
|
|
for (i = REMAINING_SIDS_INDEX; i < S; i++) {
|
|
make_group_sid(&token_sids[i], 1000 + i);
|
|
}
|
|
|
|
/* Sorted copy: sort tail sids[2..] */
|
|
memcpy(token_sids_sorted, token_sids, S * sizeof(dom_sid_t));
|
|
qsort(&token_sids_sorted[REMAINING_SIDS_INDEX],
|
|
S - REMAINING_SIDS_INDEX,
|
|
sizeof(dom_sid_t),
|
|
dom_sid_qsort_cmp);
|
|
|
|
/* ACE trustees: A SIDs, some in token, some not */
|
|
ace_sids = (dom_sid_t *)malloc(A * sizeof(dom_sid_t));
|
|
assert(ace_sids != NULL);
|
|
for (i = 0; i < A; i++) {
|
|
/* half the ACEs match a token SID, half do not */
|
|
if (i % 2 == 0 && i/2 < S) {
|
|
make_group_sid(&ace_sids[i], 1000 + i/2); /* in token */
|
|
} else {
|
|
make_group_sid(&ace_sids[i], 9000 + i); /* not in token */
|
|
}
|
|
}
|
|
|
|
/* Correctness check: both must produce the same results */
|
|
for (i = 0; i < A; i++) {
|
|
bool lin = token_has_sid_linear(token_sids, S, &ace_sids[i]);
|
|
bool srt = token_has_sid_sorted(token_sids_sorted, S, &ace_sids[i]);
|
|
if (lin != srt) {
|
|
fprintf(stderr, "FAIL: correctness mismatch at ACE %d\n", i);
|
|
return 1;
|
|
}
|
|
}
|
|
printf("PASS: correctness check (linear == sorted for all %d ACEs)\n", A);
|
|
|
|
/* ---- Benchmark: unpatched linear scan ---- */
|
|
t0 = now_ms();
|
|
for (int iter = 0; iter < ITERS; iter++) {
|
|
for (i = 0; i < A; i++) {
|
|
if (token_has_sid_linear(token_sids, S, &ace_sids[i])) hits++;
|
|
}
|
|
}
|
|
t1 = now_ms();
|
|
linear_ms = t1 - t0;
|
|
|
|
/* ---- Benchmark: patched binary search ---- */
|
|
hits = 0;
|
|
t0 = now_ms();
|
|
for (int iter = 0; iter < ITERS; iter++) {
|
|
for (i = 0; i < A; i++) {
|
|
if (token_has_sid_sorted(token_sids_sorted, S, &ace_sids[i])) hits++;
|
|
}
|
|
}
|
|
t1 = now_ms();
|
|
sorted_ms = t1 - t0;
|
|
|
|
printf("BENCH: S=%d SIDs, A=%d ACEs, %d simulated file accesses\n",
|
|
S, A, ITERS);
|
|
printf(" Unpatched (O(A*S) linear): %.1f ms\n", linear_ms);
|
|
printf(" Patched (O(A*logS) bsearch): %.1f ms\n", sorted_ms);
|
|
printf(" Speedup: %.1fx\n", linear_ms / sorted_ms);
|
|
printf(" (hits=%lu, prevents DCE)\n", (unsigned long)hits);
|
|
|
|
if (sorted_ms >= linear_ms * 0.5) {
|
|
fprintf(stderr, "WARN: expected significant speedup, got %.1fx\n",
|
|
linear_ms / sorted_ms);
|
|
/* Not a hard failure - timing is environment-dependent */
|
|
}
|
|
|
|
printf("PASS: samba-0001 benchmark complete\n");
|
|
|
|
free(token_sids);
|
|
free(token_sids_sorted);
|
|
free(ace_sids);
|
|
return 0;
|
|
}
|