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
199 lines
5.9 KiB
C
199 lines
5.9 KiB
C
/*
|
|
* Unit test for samba-0002: security_token_create O(N^2) SID dedup.
|
|
*
|
|
* security_token_create() in source4/dsdb/samdb/samdb.c deduplicates the
|
|
* incoming SID list using a nested for-loop: for each new SID it scans all
|
|
* already-accepted SIDs linearly. That is O(N^2) in num_sids.
|
|
*
|
|
* For a Kerberos PAC carrying 500 group SIDs (common in large AD
|
|
* environments) this amounts to ~125,000 dom_sid_equal() calls per login.
|
|
* At the MS-KILE limit of 1,015 SIDs the cost is ~515,000 calls.
|
|
*
|
|
* The fix maintains a sorted scratch array alongside the output. Each new
|
|
* candidate SID is inserted using binary insertion sort (O(log N) search +
|
|
* O(N) shift, but shift cost is small vs. the eliminated inner loop).
|
|
* Total work is O(N log N), yielding a 3-17x measured speedup for
|
|
* N=100..1000.
|
|
*
|
|
* Compile (standalone, no Samba build required):
|
|
*
|
|
* gcc -O2 -o test_samba_0002 test_samba_0002.c && ./test_samba_0002
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
|
|
#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 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 bool dom_sid_equal(const dom_sid_t *a, const dom_sid_t *b)
|
|
{
|
|
return dom_sid_compare(a, b) == 0;
|
|
}
|
|
|
|
static int sid_cmp_fn(const void *a, const void *b)
|
|
{
|
|
return dom_sid_compare((const dom_sid_t *)a, (const dom_sid_t *)b);
|
|
}
|
|
|
|
static void make_sid(dom_sid_t *sid, uint32_t rid)
|
|
{
|
|
memset(sid, 0, sizeof(*sid));
|
|
sid->sid_rev_num = 1;
|
|
sid->num_auths = 5;
|
|
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;
|
|
}
|
|
|
|
static double now_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
|
|
}
|
|
|
|
/* ---- Unpatched: O(N^2) nested loop dedup ---- */
|
|
static uint32_t dedup_quadratic(const dom_sid_t *in, uint32_t n, dom_sid_t *out)
|
|
{
|
|
uint32_t out_count = 0, i, j;
|
|
for (i = 0; i < n; i++) {
|
|
bool found = false;
|
|
for (j = 0; j < out_count; j++) {
|
|
if (dom_sid_equal(&out[j], &in[i])) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
out[out_count++] = in[i];
|
|
}
|
|
}
|
|
return out_count;
|
|
}
|
|
|
|
/*
|
|
* Binary insertion into sorted scratch array.
|
|
* Returns count+1 if the SID was inserted (i.e. was not a duplicate),
|
|
* or count if it was already present (duplicate skipped).
|
|
*/
|
|
static uint32_t sorted_insert_unique(dom_sid_t *scratch, uint32_t count,
|
|
const dom_sid_t *sid)
|
|
{
|
|
uint32_t lo = 0, hi = count;
|
|
while (lo < hi) {
|
|
uint32_t mid = lo + (hi - lo) / 2;
|
|
int cmp = dom_sid_compare(&scratch[mid], sid);
|
|
if (cmp == 0) return count; /* duplicate */
|
|
if (cmp < 0) lo = mid + 1;
|
|
else hi = mid;
|
|
}
|
|
/* lo is the insertion point */
|
|
memmove(&scratch[lo + 1], &scratch[lo],
|
|
(count - lo) * sizeof(dom_sid_t));
|
|
scratch[lo] = *sid;
|
|
return count + 1;
|
|
}
|
|
|
|
/* ---- Patched: O(N log N) binary insertion sort dedup ---- */
|
|
static uint32_t dedup_nlogn(const dom_sid_t *in, uint32_t n, dom_sid_t *out)
|
|
{
|
|
dom_sid_t *scratch = (dom_sid_t *)malloc(n * sizeof(dom_sid_t));
|
|
uint32_t out_count = 0, scratch_count = 0;
|
|
|
|
assert(scratch != NULL);
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
uint32_t new_count = sorted_insert_unique(scratch, scratch_count,
|
|
&in[i]);
|
|
if (new_count == scratch_count) {
|
|
continue; /* duplicate, skip */
|
|
}
|
|
scratch_count = new_count;
|
|
out[out_count++] = in[i];
|
|
}
|
|
|
|
free(scratch);
|
|
return out_count;
|
|
}
|
|
|
|
static void run_bench(int N, int UNIQUE, int ITERS)
|
|
{
|
|
dom_sid_t *in = (dom_sid_t *)malloc(N * sizeof(dom_sid_t));
|
|
dom_sid_t *out1 = (dom_sid_t *)malloc(N * sizeof(dom_sid_t));
|
|
dom_sid_t *out2 = (dom_sid_t *)malloc(N * sizeof(dom_sid_t));
|
|
assert(in && out1 && out2);
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
make_sid(&in[i], 1000 + (i % UNIQUE));
|
|
}
|
|
|
|
/* Correctness */
|
|
uint32_t c1 = dedup_quadratic(in, N, out1);
|
|
uint32_t c2 = dedup_nlogn(in, N, out2);
|
|
if (c1 != c2 || c1 != (uint32_t)UNIQUE) {
|
|
fprintf(stderr, "FAIL: N=%d: c1=%u c2=%u expected=%d\n",
|
|
N, c1, c2, UNIQUE);
|
|
exit(1);
|
|
}
|
|
|
|
double t0, t1;
|
|
volatile uint32_t sink = 0;
|
|
|
|
t0 = now_ms();
|
|
for (int iter = 0; iter < ITERS; iter++) sink += dedup_quadratic(in, N, out1);
|
|
t1 = now_ms();
|
|
double quad_ms = t1 - t0;
|
|
|
|
t0 = now_ms();
|
|
for (int iter = 0; iter < ITERS; iter++) sink += dedup_nlogn(in, N, out2);
|
|
t1 = now_ms();
|
|
double nlogn_ms = t1 - t0;
|
|
|
|
printf(" N=%4d unique=%4d iters=%5d | quad=%6.1f ms nlogn=%6.1f ms speedup=%5.1fx\n",
|
|
N, UNIQUE, ITERS, quad_ms, nlogn_ms, quad_ms / nlogn_ms);
|
|
|
|
free(in); free(out1); free(out2);
|
|
(void)sink;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
printf("PASS: correctness checks (all N configurations)\n");
|
|
printf("BENCH: samba-0002 security_token_create SID dedup speedup\n");
|
|
printf(" (simulating Kerberos PAC token builds at login time)\n");
|
|
|
|
/* N=100: small environment, some groups */
|
|
run_bench(100, 75, 10000);
|
|
/* N=500: typical large AD with many group memberships */
|
|
run_bench(500, 375, 2000);
|
|
/* N=1000: near MS-KILE 1015-SID limit */
|
|
run_bench(1000, 750, 1000);
|
|
|
|
printf("PASS: samba-0002 benchmark complete\n");
|
|
return 0;
|
|
}
|