]>
Commit | Line | Data |
---|---|---|
d8f41ccd | 1 | /* |
5c19dc3a | 2 | * Copyright (c) 2013-2015 Apple Inc. All Rights Reserved. |
d8f41ccd A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | ||
5c19dc3a | 25 | #include <Security/SecureObjectSync/SOSDigestVector.h> |
d8f41ccd A |
26 | #include <utilities/SecCFError.h> |
27 | #include <utilities/SecCFWrappers.h> | |
28 | #include <utilities/comparison.h> | |
29 | #include <dispatch/dispatch.h> | |
30 | #include <stdlib.h> | |
31 | ||
32 | CFStringRef kSOSDigestVectorErrorDomain = CFSTR("com.apple.security.sos.digestvector.error"); | |
33 | ||
34 | /* SOSDigestVector code. */ | |
35 | ||
fa7225c8 A |
36 | const size_t kMaxDVCapacity = (1024*1024L); // roughly based on KVS limit, helps avoid integer overflow issues |
37 | ||
38 | static bool SOSDigestVectorEnsureCapacity(struct SOSDigestVector *dv, size_t count) { | |
39 | // Note that capacity is the number of digests it can hold, not the size in bytes | |
40 | // Already big enough. | |
41 | if (count <= dv->capacity) | |
42 | return true; | |
43 | // Too big | |
44 | if (count > kMaxDVCapacity) { | |
45 | secerror("Requesting too much space for digest vectors: %ld", count); | |
46 | return false; | |
47 | } | |
d8f41ccd | 48 | |
fa7225c8 A |
49 | size_t capacity = (dv->capacity + 16) * 3 / 2; |
50 | size_t digestSize = sizeof(*(dv->digest)); | |
51 | if (capacity < count) | |
52 | capacity = count; | |
53 | dv->digest = reallocf(dv->digest, digestSize * capacity); | |
54 | if (dv->digest == NULL) { | |
55 | dv->count = 0; | |
56 | secerror("reallocf failed requesting space for digest vectors: %ld (bytes)", digestSize * capacity); | |
57 | return false; | |
58 | } | |
59 | dv->capacity = capacity; | |
60 | return true; | |
d8f41ccd A |
61 | } |
62 | ||
63 | static void SOSDigestVectorAppendOrdered(struct SOSDigestVector *dv, const uint8_t *digest) | |
64 | { | |
fa7225c8 A |
65 | if (SOSDigestVectorEnsureCapacity(dv, dv->count + 1)) |
66 | memcpy(dv->digest[dv->count++], digest, SOSDigestSize); | |
d8f41ccd A |
67 | } |
68 | ||
69 | void SOSDigestVectorAppend(struct SOSDigestVector *dv, const uint8_t *digest) | |
70 | { | |
71 | SOSDigestVectorAppendOrdered(dv, digest); | |
72 | dv->unsorted = true; | |
73 | } | |
74 | ||
75 | static int SOSDigestCompare(const void *a, const void *b) | |
76 | { | |
77 | return memcmp(a, b, SOSDigestSize); | |
78 | } | |
79 | ||
fa7225c8 A |
80 | // Remove duplicates from sorted manifest using minimal memmove() calls |
81 | static void SOSDigestVectorUnique(struct SOSDigestVector *dv) { | |
82 | if (dv->count < 2 || dv->digest == NULL) | |
5c19dc3a A |
83 | return; |
84 | ||
85 | const uint8_t *prev = dv->digest[0]; | |
86 | uint8_t *dest = dv->digest[1]; | |
87 | const uint8_t *end = dv->digest[dv->count]; | |
88 | const uint8_t *source = dest; | |
89 | for (const uint8_t *cur = source; cur < end; cur += SOSDigestSize) { | |
90 | int delta = SOSDigestCompare(prev, cur); | |
91 | if (delta < 0) { | |
92 | // Found a properly sorted element | |
93 | // 1) Extend the current region (prev is end of region pointer) | |
94 | // 2) Reset prev | |
95 | prev = cur; | |
96 | } else if (delta > 0) { | |
97 | // DigestVector wasn't sorted! | |
98 | assert(delta <= 0); | |
99 | } else { | |
100 | // Found a duplicate element | |
101 | // 1) Finish copy for current region up to previous element | |
102 | prev += SOSDigestSize; | |
103 | if (dest != source) | |
fa7225c8 | 104 | memmove(dest, source, prev - source); |
5c19dc3a A |
105 | dest += prev - source; |
106 | // 2) Skip remaining dupes | |
107 | if (cur < end) { | |
108 | while (cur += SOSDigestSize, cur < end) { | |
109 | int delta = SOSDigestCompare(prev, cur); | |
110 | assert(delta <= 0); | |
111 | if (delta != 0) | |
112 | break; | |
113 | } | |
114 | } | |
115 | // cur now points to the first new element that hasn't yet been copied | |
116 | // 3) Set start of next region | |
117 | prev = cur; | |
118 | source = cur; | |
119 | } | |
120 | } | |
121 | ||
122 | // Copy remainder of final region | |
123 | if (source < end) { | |
124 | prev += SOSDigestSize; | |
125 | if (dest != source) | |
fa7225c8 | 126 | memmove(dest, source, prev - source); |
5c19dc3a A |
127 | dest += prev - source; |
128 | } | |
129 | dv->count = (dest - dv->digest[0]) / SOSDigestSize; | |
130 | } | |
131 | ||
132 | ||
d8f41ccd A |
133 | void SOSDigestVectorSort(struct SOSDigestVector *dv) |
134 | { | |
fa7225c8 | 135 | if (dv->unsorted && dv->digest) { |
d8f41ccd A |
136 | qsort(dv->digest, dv->count, sizeof(*dv->digest), SOSDigestCompare); |
137 | dv->unsorted = false; | |
5c19dc3a | 138 | SOSDigestVectorUnique(dv); |
d8f41ccd A |
139 | } |
140 | } | |
141 | ||
142 | void SOSDigestVectorUniqueSorted(struct SOSDigestVector *dv) | |
143 | { | |
5c19dc3a | 144 | // Uniqify in place (sort does this now for safety) |
d8f41ccd A |
145 | if (dv->unsorted) |
146 | SOSDigestVectorSort(dv); | |
d8f41ccd A |
147 | } |
148 | ||
149 | void SOSDigestVectorSwap(struct SOSDigestVector *dva, struct SOSDigestVector *dvb) | |
150 | { | |
151 | struct SOSDigestVector dv; | |
152 | dv = *dva; | |
153 | *dva = *dvb; | |
154 | *dvb = dv; | |
155 | } | |
156 | ||
157 | bool SOSDigestVectorContainsSorted(const struct SOSDigestVector *dv, const uint8_t *digest) | |
158 | { | |
159 | return SOSDigestVectorIndexOfSorted(dv, digest) != (size_t)-1; | |
160 | } | |
161 | ||
162 | bool SOSDigestVectorContains(struct SOSDigestVector *dv, const uint8_t *digest) | |
163 | { | |
164 | if (dv->unsorted) | |
165 | SOSDigestVectorSort(dv); | |
166 | return SOSDigestVectorContainsSorted(dv, digest); | |
167 | } | |
168 | ||
169 | size_t SOSDigestVectorIndexOfSorted(const struct SOSDigestVector *dv, const uint8_t *digest) | |
170 | { | |
fa7225c8 A |
171 | if (dv->digest) { |
172 | const void *pos = bsearch(digest, dv->digest, dv->count, sizeof(*dv->digest), SOSDigestCompare); | |
173 | return pos ? ((size_t)(pos - (void *)dv->digest)) / SOSDigestSize : ((size_t)-1); | |
174 | } else { | |
175 | return -1; | |
176 | } | |
d8f41ccd A |
177 | } |
178 | ||
179 | size_t SOSDigestVectorIndexOf(struct SOSDigestVector *dv, const uint8_t *digest) | |
180 | { | |
181 | if (dv->unsorted) | |
182 | SOSDigestVectorSort(dv); | |
183 | return SOSDigestVectorIndexOfSorted(dv, digest); | |
184 | } | |
185 | ||
186 | void SOSDigestVectorFree(struct SOSDigestVector *dv) | |
187 | { | |
188 | free(dv->digest); | |
189 | dv->digest = NULL; | |
190 | dv->count = 0; | |
191 | dv->capacity = 0; | |
192 | dv->unsorted = false; | |
193 | } | |
194 | ||
195 | void SOSDigestVectorApplySorted(const struct SOSDigestVector *dv, SOSDigestVectorApplyBlock with) | |
196 | { | |
197 | bool stop = false; | |
fa7225c8 | 198 | for (size_t ix = 0; !stop && ix < dv->count && dv->digest; ++ix) { |
d8f41ccd A |
199 | with(dv->digest[ix], &stop); |
200 | } | |
201 | } | |
202 | ||
203 | void SOSDigestVectorApply(struct SOSDigestVector *dv, SOSDigestVectorApplyBlock with) | |
204 | { | |
205 | if (dv->unsorted) | |
206 | SOSDigestVectorSort(dv); | |
207 | SOSDigestVectorApplySorted(dv, with); | |
208 | } | |
209 | ||
210 | // TODO: Check for NDEBUG to disable skip dupes are release time. | |
211 | //#define SOSDVSKIPDUPES 0 | |
212 | #define SOSDVSKIPDUPES 1 | |
213 | ||
214 | #if SOSDVSKIPDUPES | |
215 | #define SOSDVINCRIX(dv,ix) (SOSDigestVectorIncrementAndSkipDupes(dv,ix)) | |
d8f41ccd | 216 | |
5c19dc3a | 217 | static size_t SOSIncrementAndSkipDupes(const uint8_t *digests, size_t count, const size_t ix) { |
d8f41ccd | 218 | size_t new_ix = ix; |
fa7225c8 | 219 | if (digests && new_ix < count) { |
5c19dc3a A |
220 | while (++new_ix < count) { |
221 | int delta = SOSDigestCompare(digests + ix * SOSDigestSize, digests + new_ix * SOSDigestSize); | |
d8f41ccd A |
222 | assert(delta <= 0); |
223 | if (delta != 0) | |
224 | break; | |
225 | } | |
226 | } | |
227 | return new_ix; | |
228 | } | |
229 | ||
5c19dc3a A |
230 | static size_t SOSDigestVectorIncrementAndSkipDupes(const struct SOSDigestVector *dv, const size_t ix) { |
231 | return SOSIncrementAndSkipDupes((const uint8_t *)dv->digest, dv->count, ix); | |
232 | } | |
233 | ||
d8f41ccd | 234 | void SOSDigestVectorAppendMultipleOrdered(struct SOSDigestVector *dv, |
5c19dc3a | 235 | size_t count, const uint8_t *digests) { |
d8f41ccd A |
236 | size_t ix = 0; |
237 | while (ix < count) { | |
238 | SOSDigestVectorAppendOrdered(dv, digests + (ix * SOSDigestSize)); | |
5c19dc3a | 239 | ix = SOSIncrementAndSkipDupes(digests, count, ix); |
d8f41ccd | 240 | } |
5c19dc3a A |
241 | } |
242 | ||
243 | #else /* !SOSDVSKIPDUPES */ | |
244 | ||
245 | #define SOSDVINCRIX(dv,ix) (ix + 1) | |
246 | ||
247 | void SOSDigestVectorAppendMultipleOrdered(struct SOSDigestVector *dv, | |
248 | size_t count, const uint8_t *digests) { | |
d8f41ccd | 249 | if (count) { |
fa7225c8 A |
250 | if (SOSDigestVectorEnsureCapacity(dv, dv->count + count)) |
251 | memcpy(dv->digest[dv->count], digests, count * SOSDigestSize); | |
d8f41ccd A |
252 | dv->count += count; |
253 | } | |
d8f41ccd A |
254 | } |
255 | ||
5c19dc3a A |
256 | #endif /* !SOSDVSKIPDUPES */ |
257 | ||
d8f41ccd A |
258 | void SOSDigestVectorIntersectSorted(const struct SOSDigestVector *dv1, const struct SOSDigestVector *dv2, |
259 | struct SOSDigestVector *dvintersect) | |
260 | { | |
261 | /* dvintersect should be empty to start. */ | |
262 | assert(dvintersect->count == 0); | |
263 | size_t i1 = 0, i2 = 0; | |
264 | while (i1 < dv1->count && i2 < dv2->count) { | |
265 | int delta = SOSDigestCompare(dv1->digest[i1], dv2->digest[i2]); | |
266 | if (delta == 0) { | |
267 | SOSDigestVectorAppendOrdered(dvintersect, dv1->digest[i1]); | |
268 | i1 = SOSDVINCRIX(dv1, i1); | |
269 | i2 = SOSDVINCRIX(dv2, i2); | |
270 | } else if (delta < 0) { | |
271 | i1 = SOSDVINCRIX(dv1, i1); | |
272 | } else { | |
273 | i2 = SOSDVINCRIX(dv2, i2); | |
274 | } | |
275 | } | |
276 | } | |
277 | ||
278 | void SOSDigestVectorUnionSorted(const struct SOSDigestVector *dv1, const struct SOSDigestVector *dv2, | |
279 | struct SOSDigestVector *dvunion) | |
280 | { | |
281 | /* dvunion should be empty to start. */ | |
282 | assert(dvunion->count == 0); | |
283 | size_t i1 = 0, i2 = 0; | |
284 | while (i1 < dv1->count && i2 < dv2->count) { | |
285 | int delta = SOSDigestCompare(dv1->digest[i1], dv2->digest[i2]); | |
286 | if (delta == 0) { | |
287 | SOSDigestVectorAppendOrdered(dvunion, dv1->digest[i1]); | |
288 | i1 = SOSDVINCRIX(dv1, i1); | |
289 | i2 = SOSDVINCRIX(dv2, i2); | |
290 | } else if (delta < 0) { | |
291 | SOSDigestVectorAppendOrdered(dvunion, dv1->digest[i1]); | |
292 | i1 = SOSDVINCRIX(dv1, i1); | |
293 | } else { | |
294 | SOSDigestVectorAppendOrdered(dvunion, dv2->digest[i2]); | |
295 | i2 = SOSDVINCRIX(dv2, i2); | |
296 | } | |
297 | } | |
298 | SOSDigestVectorAppendMultipleOrdered(dvunion, dv1->count - i1, dv1->digest[i1]); | |
299 | SOSDigestVectorAppendMultipleOrdered(dvunion, dv2->count - i2, dv2->digest[i2]); | |
300 | } | |
301 | ||
302 | void SOSDigestVectorDiffSorted(const struct SOSDigestVector *dv1, const struct SOSDigestVector *dv2, | |
303 | struct SOSDigestVector *dv1_2, struct SOSDigestVector *dv2_1) | |
304 | { | |
305 | /* dv1_2 and dv2_1 should be empty to start. */ | |
306 | assert(dv1_2->count == 0); | |
307 | assert(dv2_1->count == 0); | |
308 | ||
309 | size_t i1 = 0, i2 = 0; | |
310 | while (i1 < dv1->count && i2 < dv2->count) { | |
311 | int delta = SOSDigestCompare(dv1->digest[i1], dv2->digest[i2]); | |
312 | if (delta == 0) { | |
313 | i1 = SOSDVINCRIX(dv1, i1); | |
314 | i2 = SOSDVINCRIX(dv2, i2); | |
315 | } else if (delta < 0) { | |
316 | SOSDigestVectorAppendOrdered(dv1_2, dv1->digest[i1]); | |
317 | i1 = SOSDVINCRIX(dv1, i1); | |
318 | } else { | |
319 | SOSDigestVectorAppendOrdered(dv2_1, dv2->digest[i2]); | |
320 | i2 = SOSDVINCRIX(dv2, i2); | |
321 | } | |
322 | } | |
323 | SOSDigestVectorAppendMultipleOrdered(dv1_2, dv1->count - i1, dv1->digest[i1]); | |
324 | SOSDigestVectorAppendMultipleOrdered(dv2_1, dv2->count - i2, dv2->digest[i2]); | |
325 | } | |
326 | ||
327 | void SOSDigestVectorDiff(struct SOSDigestVector *dv1, struct SOSDigestVector *dv2, | |
328 | struct SOSDigestVector *dv1_2, struct SOSDigestVector *dv2_1) | |
329 | { | |
330 | if (dv1->unsorted) SOSDigestVectorSort(dv1); | |
331 | if (dv2->unsorted) SOSDigestVectorSort(dv2); | |
332 | SOSDigestVectorDiffSorted(dv1, dv2, dv1_2, dv2_1); | |
333 | } | |
334 | ||
335 | /* | |
336 | If A and B are sets, then the relative complement of A in B, also termed the set-theoretic difference of B and A, | |
337 | is the set of elements in B, but not in A. The relative complement of A in B is denoted B ∖ A according to the ISO 31-11 standard | |
338 | sometimes written B − A | |
339 | ||
340 | The common case for us will be Removals\Additions | |
341 | */ | |
342 | ||
343 | static void SOSDigestVectorAppendComplementAtIndex(size_t a_ix, const struct SOSDigestVector *dvA, size_t b_ix, const struct SOSDigestVector *dvB, | |
344 | struct SOSDigestVector *dvcomplement) | |
345 | { | |
346 | assert(a_ix <= dvA->count && b_ix <= dvB->count); | |
fa7225c8 | 347 | while (a_ix < dvA->count && b_ix < dvB->count && dvA->digest && dvB->digest) { |
d8f41ccd A |
348 | int delta = SOSDigestCompare(dvA->digest[a_ix], dvB->digest[b_ix]); |
349 | if (delta == 0) { | |
350 | a_ix = SOSDVINCRIX(dvA, a_ix); | |
351 | b_ix = SOSDVINCRIX(dvB, b_ix); | |
352 | } else if (delta < 0) { | |
353 | a_ix = SOSDVINCRIX(dvA, a_ix); | |
354 | } else { | |
355 | SOSDigestVectorAppendOrdered(dvcomplement, dvB->digest[b_ix]); | |
356 | b_ix = SOSDVINCRIX(dvB, b_ix); | |
357 | } | |
358 | } | |
fa7225c8 A |
359 | if (dvB->digest) |
360 | SOSDigestVectorAppendMultipleOrdered(dvcomplement, dvB->count - b_ix, dvB->digest[b_ix]); | |
d8f41ccd A |
361 | } |
362 | ||
363 | ||
364 | void SOSDigestVectorComplementSorted(const struct SOSDigestVector *dvA, const struct SOSDigestVector *dvB, | |
365 | struct SOSDigestVector *dvcomplement) | |
366 | { | |
367 | /* dvcomplement should be empty to start. */ | |
368 | assert(dvcomplement->count == 0); | |
369 | assert(!dvA->unsorted); | |
370 | assert(!dvB->unsorted); | |
371 | ||
372 | SOSDigestVectorAppendComplementAtIndex(0, dvA, 0, dvB, dvcomplement); | |
373 | } | |
374 | ||
375 | ||
376 | /* | |
377 | For each item in base | |
378 | ||
379 | one way to do would be to define SOSDigestVectorComplementSorted | |
380 | ||
381 | For removals, if removal value is less than base, increment until GEQ | |
382 | */ | |
383 | bool SOSDigestVectorPatchSorted(const struct SOSDigestVector *base, const struct SOSDigestVector *removals, | |
384 | const struct SOSDigestVector *additions, struct SOSDigestVector *dv, | |
385 | CFErrorRef *error) | |
386 | { | |
387 | /* dv should be empty to start. */ | |
388 | assert(dv->count == 0); | |
389 | assert(!base->unsorted); | |
390 | assert(!removals->unsorted); | |
391 | assert(!additions->unsorted); | |
392 | ||
393 | size_t i1 = 0, i2 = 0, i3 = 0; | |
394 | while (i1 < base->count && i2 < additions->count) { | |
395 | // Pick the smaller of base->digest[i1] and additions->digest[i2] as a | |
396 | // candidate to be put into the output vector. If udelta positive, addition is smaller | |
397 | int udelta = SOSDigestCompare(base->digest[i1], additions->digest[i2]); | |
398 | const uint8_t *candidate = udelta < 0 ? base->digest[i1] : additions->digest[i2]; | |
399 | ||
400 | // ddelta > 0 means rem > candidate | |
401 | int ddelta = 1; | |
402 | while (i3 < removals->count) { | |
403 | ddelta = SOSDigestCompare(removals->digest[i3], candidate); | |
404 | if (ddelta < 0) { | |
405 | i3 = SOSDVINCRIX(removals, i3); | |
406 | } else { | |
407 | if (ddelta == 0) | |
408 | i3 = SOSDVINCRIX(removals, i3); | |
409 | break; | |
410 | } | |
411 | } | |
412 | if (ddelta > 0) | |
413 | SOSDigestVectorAppendOrdered(dv, candidate); | |
414 | ||
415 | // Point to next (different) candidate | |
416 | if (udelta == 0) { | |
417 | i1 = SOSDVINCRIX(base, i1); | |
418 | i2 = SOSDVINCRIX(additions, i2); | |
419 | } else if (udelta < 0) { | |
420 | i1 = SOSDVINCRIX(base, i1); | |
421 | } else { | |
422 | i2 = SOSDVINCRIX(additions, i2); | |
423 | } | |
424 | } | |
425 | SOSDigestVectorAppendComplementAtIndex(i3, removals, i1, base, dv); | |
426 | SOSDigestVectorAppendComplementAtIndex(i3, removals, i2, additions, dv); | |
427 | ||
428 | return true; | |
429 | } | |
430 | ||
431 | bool SOSDigestVectorPatch(struct SOSDigestVector *base, struct SOSDigestVector *removals, | |
432 | struct SOSDigestVector *additions, struct SOSDigestVector *dv, | |
433 | CFErrorRef *error) | |
434 | { | |
435 | if (base->unsorted) SOSDigestVectorSort(base); | |
436 | if (removals->unsorted) SOSDigestVectorSort(removals); | |
437 | if (additions->unsorted) SOSDigestVectorSort(additions); | |
438 | return SOSDigestVectorPatchSorted(base, removals, additions, dv, error); | |
439 | } |