Safe Data API
CRC-protected (optionally redundant) data containers for Zephyr
v0.0-dev+gbb519af
Loading...
Searching...
No Matches
safe_data.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2026 The safety-toolbox contributors
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Type-erased core for the Safe Data API. All the integrity logic (CRC choice,
6 * redundancy/recovery, fault reaction) lives here in exactly one place so it can
7 * be audited once; the typed macros in safe_data.h merely forward to it.
8 */
9
10#include <string.h>
11
12#include <zephyr/kernel.h>
13#include <zephyr/init.h>
14#include <zephyr/sys/crc.h>
15#include <zephyr/logging/log.h>
16
17#include <safe_data/safe_data.h>
18
19LOG_MODULE_REGISTER(safe_data, CONFIG_SAFE_DATA_LOG_LEVEL);
20
21/* --- CRC backend selection (default to CRC32-IEEE if Kconfig absent) ------
22 * Exposed as a seed + chainable update pair so the tag can also be computed
23 * over data that is transformed on the fly (see safe_tag_inverted()).
24 */
25#if defined(CONFIG_SAFE_DATA_CRC16_CCITT)
26#define SAFE_TAG_SEED 0xFFFFu
27static inline uint32_t safe_tag_update(uint32_t c, const uint8_t *p, size_t len)
28{
29 return crc16_ccitt((uint16_t)c, p, len);
30}
31#elif defined(CONFIG_SAFE_DATA_CRC8_CCITT)
32#define SAFE_TAG_SEED 0xFFu
33static inline uint32_t safe_tag_update(uint32_t c, const uint8_t *p, size_t len)
34{
35 return crc8_ccitt((uint8_t)c, p, len);
36}
37#else /* CONFIG_SAFE_DATA_CRC32_IEEE or standalone default */
38#define SAFE_TAG_SEED 0x0u
39static inline uint32_t safe_tag_update(uint32_t c, const uint8_t *p, size_t len)
40{
41 return crc32_ieee_update(c, p, len);
42}
43#endif
44
45static inline uint32_t safe_tag(const void *p, size_t len)
46{
47 return safe_tag_update(SAFE_TAG_SEED, p, len);
48}
49
50/* Tag of the payload the shadow implies (~shadow), computed chunk-wise so the
51 * payload itself is never touched. Lets verification stay read-only.
52 */
53static uint32_t safe_tag_inverted(const void *shadow, size_t len)
54{
55 const uint8_t *sh = shadow;
56 uint32_t c = SAFE_TAG_SEED;
57 uint8_t tmp[32];
58
59 while (len > 0) {
60 size_t n = MIN(len, sizeof(tmp));
61
62 for (size_t i = 0; i < n; i++) {
63 tmp[i] = (uint8_t)~sh[i];
64 }
65 c = safe_tag_update(c, tmp, n);
66 sh += n;
67 len -= n;
68 }
69
70 return c;
71}
72
73/* Does the live payload still match the shadow (payload == ~shadow)? */
74static bool payload_matches_shadow(const void *payload, const void *shadow,
75 size_t len)
76{
77 const uint8_t *pl = payload;
78 const uint8_t *sh = shadow;
79
80 for (size_t i = 0; i < len; i++) {
81 if (pl[i] != (uint8_t)~sh[i]) {
82 return false;
83 }
84 }
85
86 return true;
87}
88
89/* --- Fault/diagnostic event sink (centralised policy) --------------------- */
90__weak void safe_data_fault_handler(const void *payload, size_t len)
91{
92 LOG_ERR("integrity fault: payload %p, %zu bytes", payload, len);
93}
94
96
98{
99 if (cb != NULL && fault_cb != NULL && fault_cb != cb) {
100 return -EALREADY;
101 }
102 fault_cb = cb;
103
104 return 0;
105}
106
107#define SAFE_DATA_KIND_COUNT (SAFE_DATA_FAULT_KIND_MUTATOR_CLAMPED + 1)
108
109#if defined(CONFIG_SAFE_DATA_STATS)
111
113{
114 if (out == NULL) {
115 return -EINVAL;
116 }
117
118 out->unrecoverable = (uint32_t)atomic_get(
120 out->recovered_tag = (uint32_t)atomic_get(
122 out->recovered_payload = (uint32_t)atomic_get(
124 out->lock_timeouts = (uint32_t)atomic_get(
126 out->overwritten = (uint32_t)atomic_get(
128 out->mutator_clamped = (uint32_t)atomic_get(
130
131 return 0;
132}
133
135{
136 for (size_t i = 0; i < ARRAY_SIZE(stat_counters); i++) {
137 atomic_clear(&stat_counters[i]);
138 }
139}
140#else /* !CONFIG_SAFE_DATA_STATS */
142{
143 if (out == NULL) {
144 return -EINVAL;
145 }
146
147 return -ENOTSUP;
148}
149
150void safe_data_stats_reset(void)
151{
152}
153#endif /* CONFIG_SAFE_DATA_STATS */
154
155/* Set during the self-test so its deliberate corruption neither pollutes the
156 * statistics nor triggers the configured fault reaction. Only written by
157 * safe_data_selftest(), which is meant to run from boot/diagnostic context.
158 */
159static volatile bool event_mute;
160
161/* Every integrity event funnels through here: statistics, the registered
162 * callback (all kinds), and the configured control-flow reaction
163 * (unrecoverable faults only).
164 *
165 * NOTE: may run while the affected container's lock is held - the callback
166 * contract (see safe_data_fault_cb_t) forbids blocking or touching containers.
167 */
168static void safe_event(enum safe_data_fault_kind kind, const void *payload,
169 size_t len)
170{
171 if (event_mute) {
172 return;
173 }
174
175#if defined(CONFIG_SAFE_DATA_STATS)
176 atomic_inc(&stat_counters[kind]);
177#endif
178
179 if (fault_cb != NULL) {
180 const struct safe_data_fault_info info = {
181 .kind = kind,
182 .payload = payload,
183 .len = len,
184 };
185
186 fault_cb(&info);
187 }
188
190 if (IS_ENABLED(CONFIG_SAFE_DATA_FAULT_PANIC)) {
191 LOG_ERR("integrity fault -> k_panic()");
192 k_panic();
193 } else if (IS_ENABLED(CONFIG_SAFE_DATA_FAULT_HANDLER) &&
194 fault_cb == NULL) {
196 }
197 /* CONFIG_SAFE_DATA_FAULT_RETURN: caller inspects the code. */
198 }
199}
200
201/* Lock-timeout reporting hook for the SAFE_SECTION macro. */
202void z_safe_data_lock_failed(void)
203{
205}
206
207/* --- Sealing (write the integrity tag, refresh the shadow copy) ----------- */
208void z_safe_data_seal(void *payload, size_t len, uint32_t *crc, void *shadow)
209{
210 *crc = safe_tag(payload, len);
211
212 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT) && shadow != NULL) {
213 const uint8_t *src = payload;
214 uint8_t *dst = shadow;
215
216 for (size_t i = 0; i < len; i++) {
217 dst[i] = (uint8_t)~src[i];
218 }
219 }
220}
221
222/* ------------------------------------------------------------------------- */
223
224int safe_data_init(struct k_mutex *lock, void *payload, size_t len,
225 uint32_t *crc, void *shadow)
226{
227 if (payload == NULL || crc == NULL || len == 0) {
228 return -EINVAL;
229 }
230
231 if (lock != NULL) {
232 k_mutex_init(lock);
233 }
234 z_safe_data_seal(payload, len, crc, shadow);
235
236 return 0;
237}
238
239/* Pure diagnosis with no side effects at all (no events, no logging). The
240 * public entry points wrap this and report exactly one event per detection.
241 */
242static int verify_quiet(const void *payload, size_t len, uint32_t crc,
243 const void *shadow)
244{
245 if (payload == NULL || len == 0) {
246 return -EINVAL;
247 }
248
249 if (safe_tag(payload, len) == crc) {
250 return 0; /* primary copy intact */
251 }
252
253 /* Primary CRC mismatch - is the fault recoverable from the shadow copy?
254 * The actual in-place repair happens in safe_data_verify_repair(),
255 * under the container lock.
256 */
257 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT) && shadow != NULL) {
258 if (payload_matches_shadow(payload, shadow, len)) {
259 /* Payload and shadow are consistent => only the stored
260 * integrity tag was corrupted. The data itself is fine.
261 */
262 return SAFE_DATA_RECOVERED;
263 }
264
265 /* They disagree: does the payload the shadow implies (~shadow)
266 * match the stored tag? If so, the shadow is the good copy.
267 * (If not, the data is lost either way.)
268 */
269 if (safe_tag_inverted(shadow, len) == crc) {
270 return SAFE_DATA_RECOVERED;
271 }
272 }
273
274 return -EILSEQ;
275}
276
277int safe_data_verify(const void *payload, size_t len, uint32_t crc,
278 const void *shadow)
279{
280 int ret = verify_quiet(payload, len, crc, shadow);
281
282 if (ret == -EILSEQ) {
283 LOG_ERR("corruption detected; unrecoverable");
285 }
286
287 return ret;
288}
289
290int safe_data_verify_repair(void *payload, size_t len, uint32_t crc,
291 void *shadow)
292{
293 int ret = verify_quiet(payload, len, crc, shadow);
294
295 if (ret == -EILSEQ) {
296 LOG_ERR("payload and shadow both corrupt; unrecoverable");
298 return ret;
299 }
300 if (ret != SAFE_DATA_RECOVERED) {
301 return ret; /* 0 or -EINVAL */
302 }
303
304 /* Recoverable fault: repair the payload in place. Only reached with
305 * redundancy enabled and a shadow present (verify_quiet cannot return
306 * SAFE_DATA_RECOVERED otherwise). The caller reseals the stored tag to
307 * make the repair durable.
308 */
309 if (payload_matches_shadow(payload, shadow, len)) {
310 LOG_WRN("integrity tag stale; data consistent, will reseal");
312 } else {
313 const uint8_t *sh = shadow;
314 uint8_t *pl = payload;
315
316 for (size_t i = 0; i < len; i++) {
317 pl[i] = (uint8_t)~sh[i];
318 }
319 LOG_WRN("payload corrupt; recovered from shadow copy");
321 }
322
323 return SAFE_DATA_RECOVERED;
324}
325
326int safe_data_read(struct k_mutex *lock, void *payload, size_t len,
327 uint32_t *crc, void *shadow, void *out)
328{
329 int ret;
330
331 if (payload == NULL || crc == NULL || out == NULL || len == 0) {
332 return -EINVAL;
333 }
334
335 ret = z_safe_lock(lock);
336 if (ret != 0) {
338 return ret;
339 }
340 ret = safe_data_verify_repair(payload, len, *crc, shadow);
341 if (ret == SAFE_DATA_RECOVERED) {
342 /* Make the recovery durable (repairs a stale stored tag). */
343 z_safe_data_seal(payload, len, crc, shadow);
344 ret = 0;
345 }
346 if (ret == 0) {
347 memcpy(out, payload, len);
348 }
349 z_safe_unlock(lock);
350
351 return ret;
352}
353
354int safe_data_write(struct k_mutex *lock, void *payload, size_t len,
355 uint32_t *crc, void *shadow, const void *in)
356{
357 int ret;
358
359 if (payload == NULL || crc == NULL || in == NULL || len == 0) {
360 return -EINVAL;
361 }
362
363 ret = z_safe_lock(lock);
364 if (ret != 0) {
366 return ret;
367 }
368
369 /* Observe-only pre-check: overwriting destroys the evidence of any
370 * pre-existing corruption, so report it first. Does not change the
371 * outcome of the write.
372 */
373 if (IS_ENABLED(CONFIG_SAFE_DATA_WRITE_CHECKS_OLD) &&
374 verify_quiet(payload, len, *crc, shadow) != 0) {
375 LOG_WRN("overwriting corrupt contents");
377 }
378
379 memcpy(payload, in, len);
380 z_safe_data_seal(payload, len, crc, shadow);
381 z_safe_unlock(lock);
382
383 return 0;
384}
385
386int safe_data_update(struct k_mutex *lock, void *payload, size_t len,
387 uint32_t *crc, void *shadow,
388 safe_data_mutator_t fn, void *user)
389{
390 int ret;
391
392 if (payload == NULL || crc == NULL || fn == NULL || len == 0) {
393 return -EINVAL;
394 }
395
396 ret = z_safe_lock(lock);
397 if (ret != 0) {
399 return ret;
400 }
401
402 ret = safe_data_verify_repair(payload, len, *crc, shadow);
403 if (ret == SAFE_DATA_RECOVERED) {
404 /* Pre-check repaired the data in place; make it durable. */
405 z_safe_data_seal(payload, len, crc, shadow);
406 ret = 0;
407 }
408
409 if (ret == 0) {
410 int mret = fn(payload, user);
411
412 if (mret > 0) {
413 /* Contract violation: only 0 (commit) or a negative
414 * errno (abort) are legal. Treat as an abort so a
415 * confused mutator can never commit by accident.
416 */
417 LOG_WRN("mutator returned %d; clamped to -EINVAL", mret);
419 payload, len);
420 mret = -EINVAL;
421 }
422
423 if (mret == 0) {
424 /* Commit: reseal tag (and refresh shadow). */
425 z_safe_data_seal(payload, len, crc, shadow);
426 } else {
427 /* Abort: roll back any partial writes from the shadow
428 * copy so the container is never left inconsistent. The
429 * stored tag already matches the rolled-back payload.
430 *
431 * Without a shadow copy there is no rollback buffer; the
432 * contract is then that a mutator must validate up front
433 * and not modify the payload before returning an error.
434 */
435 if (IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT) &&
436 shadow != NULL) {
437 const uint8_t *sh = shadow;
438 uint8_t *pl = payload;
439
440 for (size_t i = 0; i < len; i++) {
441 pl[i] = (uint8_t)~sh[i];
442 }
443 }
444 ret = mret;
445 }
446 }
447
448 z_safe_unlock(lock);
449
450 return ret;
451}
452
453#if defined(CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT)
454int safe_data_commit(struct k_mutex *lock, void *payload, size_t len,
455 uint32_t *crc, void *shadow)
456{
457 int ret;
458
459 if (payload == NULL || crc == NULL || len == 0) {
460 return -EINVAL;
461 }
462
463 ret = z_safe_lock(lock);
464 if (ret != 0) {
466 return ret;
467 }
468 z_safe_data_seal(payload, len, crc, shadow);
469 z_safe_unlock(lock);
470
471 return 0;
472}
473#endif /* CONFIG_SAFE_DATA_ALLOW_UNCHECKED_COMMIT */
474
475/* --- Self-test of the integrity mechanism itself (B7) ---------------------- */
476#if defined(CONFIG_SAFE_DATA_SELFTEST)
477
478/* Known-answer for the standard "123456789" check input, per backend, against
479 * the exact Zephyr implementation (crc8_ccitt: poly 0x07 MSB-first, seed 0xFF;
480 * crc16_ccitt: reflected poly 0x8408, seed 0xFFFF; crc32_ieee: reflected).
481 */
482#if defined(CONFIG_SAFE_DATA_CRC16_CCITT)
483#define SAFE_DATA_KAT_EXPECTED 0x6F91u
484#elif defined(CONFIG_SAFE_DATA_CRC8_CCITT)
485#define SAFE_DATA_KAT_EXPECTED 0xFBu
486#else /* CRC32-IEEE */
487#define SAFE_DATA_KAT_EXPECTED 0xCBF43926u
488#endif
489
491{
492 static const uint8_t kat[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
493 uint8_t buf[8] = {0xA5, 0x5A, 0x01, 0xFE, 0x10, 0x20, 0x40, 0x80};
494 uint8_t shadow[sizeof(buf)];
495 void *sh = IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT) ? shadow : NULL;
496 uint32_t crc = 0;
497 int ret = 0;
498
499 /* The deliberate corruption below must not pollute the statistics or
500 * trigger the configured fault reaction.
501 */
502 event_mute = true;
503
504 /* 1. Known-answer test: a broken CRC backend (e.g. corrupted lookup
505 * table) is the one fault everything else cannot detect.
506 */
507 if (safe_tag(kat, sizeof(kat)) != SAFE_DATA_KAT_EXPECTED) {
508 ret = -EFAULT;
509 }
510
511 /* 2. Seal + verify round trip on a scratch container. */
512 if (ret == 0) {
513 z_safe_data_seal(buf, sizeof(buf), &crc, sh);
514 if (verify_quiet(buf, sizeof(buf), crc, sh) != 0) {
515 ret = -EFAULT;
516 }
517 }
518
519 /* 3. Detection: a single flipped bit must change the tag. */
520 if (ret == 0) {
521 buf[0] ^= 0x01u;
522 if (safe_tag(buf, sizeof(buf)) == crc) {
523 ret = -EFAULT;
524 }
525 }
526
527 /* 4. Recovery (redundant only): the repair path must restore the
528 * corrupted payload from the shadow and verify clean again.
529 */
530 if (ret == 0 && IS_ENABLED(CONFIG_SAFE_DATA_REDUNDANT)) {
531 if (safe_data_verify_repair(buf, sizeof(buf), crc, sh) !=
533 buf[0] != 0xA5u ||
534 verify_quiet(buf, sizeof(buf), crc, sh) != 0) {
535 ret = -EFAULT;
536 }
537 }
538
539 event_mute = false;
540
541 if (ret != 0) {
542 LOG_ERR("integrity-mechanism self-test FAILED");
543 }
544
545 return ret;
546}
547
548#if defined(CONFIG_SAFE_DATA_SELFTEST_BOOT)
549static int boot_selftest_result = -EAGAIN;
550
552{
554}
555
557{
559 if (boot_selftest_result != 0) {
560 /* A broken integrity mechanism means no protected data can be
561 * trusted: that is unconditionally unrecoverable.
562 */
563 k_panic();
564 }
565
567}
568SYS_INIT(safe_data_boot_selftest, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
569#else /* !CONFIG_SAFE_DATA_SELFTEST_BOOT */
571{
572 return -EAGAIN; /* not run */
573}
574#endif /* CONFIG_SAFE_DATA_SELFTEST_BOOT */
575
576#endif /* CONFIG_SAFE_DATA_SELFTEST */
int safe_data_verify_repair(void *payload, size_t len, uint32_t crc, void *shadow)
Verify payload integrity and repair a recoverable fault in place.
Definition safe_data.c:290
int safe_data_update(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, safe_data_mutator_t fn, void *user)
Atomic read-modify-write: lock, verify, run fn, reseal on success.
Definition safe_data.c:386
#define SAFE_DATA_RECOVERED
Return code for a fault that the redundant shadow copy covers.
Definition safe_data.h:83
int safe_data_read(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, void *out)
Verify under lock and copy the payload out to out.
Definition safe_data.c:326
int(* safe_data_mutator_t)(void *payload, void *user)
Mutator callback used by safe_data_update() / SAFE_UPDATE().
Definition safe_data.h:98
int safe_data_verify(const void *payload, size_t len, uint32_t crc, const void *shadow)
Verify payload integrity. Pure: read-only and lock-free; it NEVER modifies the payload,...
Definition safe_data.c:277
int safe_data_write(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow, const void *in)
Copy in into the payload under lock and reseal the integrity tag.
Definition safe_data.c:354
int safe_data_init(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
Initialise the lock and seal the current payload contents.
Definition safe_data.c:224
int safe_data_commit(struct k_mutex *lock, void *payload, size_t len, uint32_t *crc, void *shadow)
Reseal after an explicit external modification (advanced use). Lock, recompute the integrity tag,...
Definition safe_data.c:454
void(* safe_data_fault_cb_t)(const struct safe_data_fault_info *info)
Fault/diagnostic event callback.
Definition safe_data.h:149
safe_data_fault_kind
Kind of integrity event reported to the fault handler / statistics.
Definition safe_data.h:114
int safe_data_selftest_result(void)
Result of the boot-time self-test (CONFIG_SAFE_DATA_SELFTEST_BOOT).
Definition safe_data.c:551
void safe_data_stats_reset(void)
Reset all statistics counters to zero.
Definition safe_data.c:134
int safe_data_stats_get(struct safe_data_stats *out)
Snapshot the global statistics.
Definition safe_data.c:112
void safe_data_fault_handler(const void *payload, size_t len)
Legacy user-overridable (weak) fault handler.
Definition safe_data.c:90
int safe_data_selftest(void)
Self-test of the integrity mechanism itself.
Definition safe_data.c:490
int safe_data_fault_handler_register(safe_data_fault_cb_t cb)
Register the fault handler callback.
Definition safe_data.c:97
@ SAFE_DATA_FAULT_KIND_UNRECOVERABLE
Definition safe_data.h:116
@ SAFE_DATA_FAULT_KIND_LOCK_TIMEOUT
Definition safe_data.h:122
@ SAFE_DATA_FAULT_KIND_RECOVERED_PAYLOAD
Definition safe_data.h:120
@ SAFE_DATA_FAULT_KIND_MUTATOR_CLAMPED
Definition safe_data.h:126
@ SAFE_DATA_FAULT_KIND_RECOVERED_TAG
Definition safe_data.h:118
@ SAFE_DATA_FAULT_KIND_OVERWRITTEN
Definition safe_data.h:124
static uint32_t safe_tag_update(uint32_t c, const uint8_t *p, size_t len)
Definition safe_data.c:39
static int safe_data_boot_selftest(void)
Definition safe_data.c:556
#define SAFE_DATA_KAT_EXPECTED
Definition safe_data.c:487
static bool payload_matches_shadow(const void *payload, const void *shadow, size_t len)
Definition safe_data.c:74
static safe_data_fault_cb_t fault_cb
Definition safe_data.c:95
#define SAFE_DATA_KIND_COUNT
Definition safe_data.c:107
static uint32_t safe_tag_inverted(const void *shadow, size_t len)
Definition safe_data.c:53
static int verify_quiet(const void *payload, size_t len, uint32_t crc, const void *shadow)
Definition safe_data.c:242
#define SAFE_TAG_SEED
Definition safe_data.c:38
static uint32_t safe_tag(const void *p, size_t len)
Definition safe_data.c:45
static int boot_selftest_result
Definition safe_data.c:549
LOG_MODULE_REGISTER(safe_data, CONFIG_SAFE_DATA_LOG_LEVEL)
static volatile bool event_mute
Definition safe_data.c:159
static void safe_event(enum safe_data_fault_kind kind, const void *payload, size_t len)
Definition safe_data.c:168
static atomic_t stat_counters[(SAFE_DATA_FAULT_KIND_MUTATOR_CLAMPED+1)]
Definition safe_data.c:110
SYS_INIT(safe_data_boot_selftest, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT)
Event descriptor passed to the registered fault handler.
Definition safe_data.h:130
enum safe_data_fault_kind kind
Definition safe_data.h:132
Global fault/recovery statistics (CONFIG_SAFE_DATA_STATS).
Definition safe_data.h:183
uint32_t unrecoverable
Definition safe_data.h:189
uint32_t overwritten
Definition safe_data.h:193
uint32_t lock_timeouts
Definition safe_data.h:191
uint32_t recovered_tag
Definition safe_data.h:185
uint32_t recovered_payload
Definition safe_data.h:187
uint32_t mutator_clamped
Definition safe_data.h:195