Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ add_executable(baseline
${APP_DIR}/storage/SemihostingDisk.c
${APP_DIR}/syslog/Syslog.c
${APP_DIR}/syslog/SyslogFields.c
${APP_DIR}/syslog/SyslogPipelineSd.c
${APP_DIR}/syslog/SyslogErrorHandler.c
$<TARGET_OBJECTS:baseline_upstream>
)
Expand Down
49 changes: 33 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,49 @@ It builds on a baseline that simulates the sort of device you might be adding th
measures itself: see [docs/baseline.md](docs/baseline.md) for what the baseline is, how the
figures are made, and how to run it.

## This stage — HMAC at rest
## This stage — Private SD-ELEMENT

Replace the CRC-16 with a keyed HMAC. The checksum established that a record came back the way it
went in; the HMAC establishes that nobody has changed it since. An edit made without the key fails
verification, so stored records become tamper-evident rather than merely intact.
Write a private enterprise SD-ELEMENT. RFC 5424 reserves this form for definitions of your own, and
`SyslogPipelineSd.c` is a complete example of one: it implements the library's structured-data
extension point in its own translation unit.

```c
struct SolidSyslogMbedTlsHmacSha256PolicyConfig hmacConfig = {.GetKey = SyslogStoreKey};
static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element)
{
(void) base;

.SecurityPolicy = SolidSyslogMbedTlsHmacSha256Policy_Create(&hmacConfig),
SolidSyslogSdElement_Begin(element, "logPipeline", SYSLOG_ENTERPRISE_NUMBER);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), "tls");
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), "hmac-sha256");
SolidSyslogSdElement_End(element);
}

static struct SolidSyslogStructuredData s_pipelineSd = {SyslogPipelineSd_Format};
```

```text
... [logPipeline@32473 transport="tls" atRest="hmac-sha256"] device started
```

The key is fetched per seal and per verify rather than held, so it never sits on the policy
instance. Key custody, rotation and provisioning are yours; the library consumes a key you supply
and never stores one.
The vtable has one entry, `Format`, and the library never allocates the object. A stateless source
therefore needs no `_Create` and no pool slot; it is a static this application owns and points the
config at. A source with per-instance state puts that state alongside the vtable in the same struct
and reads it back from the `base` parameter.

A non-zero enterprise number is what produces a private SD-ID: `_Begin` emits `name@number` for one
and a bare IANA `name` for zero. `SyslogEnterprise.h` now defines the number and derives the string
that `origin`'s `enterpriseId` carries, so the two forms cannot drift.

Holding a named symmetric key and handing it out is the device's own mechanism — a device already
doing mTLS has provisioned secrets and somewhere to keep them, so the key slot, the loader and the
accessor all sit below the line. What SolidSyslog is charged for is the policy and the callback that
reaches for the key.
What the element reports is the state of the logging path. A collector can confirm that a record
arrived over TLS and was sealed at rest, and can alert on a device whose pipeline has weakened.
The remaining stages change both values as the protection changes.

**When you need it.** If an attacker could reach the medium — removable, unattended, or stealable —
and stored records must be provably unaltered.
**When you need it.** If a collector has to verify the protection a record travelled and rested
under rather than assume it.

<!-- STAGE-COST:START (generated by scripts/gen-cost-table.py — do not edit by hand) -->

**Cost above baseline: Flash +13,416 B, RAM +37,472 B.**
**Cost above baseline: Flash +13,556 B, RAM +37,476 B.**

<!-- STAGE-COST:END -->

Expand Down Expand Up @@ -64,6 +80,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
| Origin address | the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite | +12,380 | +9,172 |
| TLS | a collector the device authenticates, and records no longer readable on the wire | +13,084 | +37,452 |
| HMAC at rest | stored records that cannot be edited undetected, not merely checked for corruption | +13,416 | +37,472 |
| Private SD-ELEMENT | a record that states the protection its own log pipeline was under | +13,556 | +37,476 |

*Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.*

Expand Down
4 changes: 3 additions & 1 deletion app/syslog/Syslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "SolidSyslogTimeQualitySd.h"
#include "SyslogEnterprise.h"
#include "SyslogFields.h"
#include "SyslogPipelineSd.h"

#include "lwip/ip4_addr.h"
#include "lwip/tcpip.h"
Expand Down Expand Up @@ -69,7 +70,7 @@ static struct SolidSyslog* s_logger = NULL;
static uint8_t s_ring[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(SYSLOG_BUFFER_RECORDS)];

/* The logger reads these on every record, so they outlive Syslog_Start. */
static struct SolidSyslogStructuredData* s_sd[3];
static struct SolidSyslogStructuredData* s_sd[4];

/* One reading at boot, then free-running on the tick — enough to stamp a record,
* not synchronisation. RFC 5424 section 7.1.3 forbids syncAccuracy alongside an
Expand Down Expand Up @@ -183,6 +184,7 @@ void Syslog_Start(void)
.GetIpAt = SyslogOriginIpAt,
};
s_sd[2] = SolidSyslogOriginSd_Create(&originConfig);
s_sd[3] = SyslogPipelineSd_Get();

struct SolidSyslogMbedTlsHmacSha256PolicyConfig hmacConfig = {.GetKey = SyslogStoreKey};

Expand Down
12 changes: 8 additions & 4 deletions app/syslog/SyslogEnterprise.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
/* This product's IANA Private Enterprise Number. It identifies the vendor, not
* the logger, so it lives on its own rather than beside any one element that
* carries it.
/* This product's IANA Private Enterprise Number, in the two forms RFC 5424 wants
* it: the number that makes a private SD-ID private, and the string origin's
* enterpriseId PARAM carries. Defined once and derived, so the two cannot drift.
*
* 32473 is reserved for documentation (RFC 5612). Register your own at
* https://www.iana.org/assignments/enterprise-numbers/ */
#ifndef APP_SYSLOG_ENTERPRISE_H
#define APP_SYSLOG_ENTERPRISE_H

#define SYSLOG_ENTERPRISE_ID "32473"
#define SYSLOG_ENTERPRISE_NUMBER 32473

#define SYSLOG_ENTERPRISE_STRINGIFY_(value) #value
#define SYSLOG_ENTERPRISE_STRINGIFY(value) SYSLOG_ENTERPRISE_STRINGIFY_(value)
#define SYSLOG_ENTERPRISE_ID SYSLOG_ENTERPRISE_STRINGIFY(SYSLOG_ENTERPRISE_NUMBER)

#endif /* APP_SYSLOG_ENTERPRISE_H */
30 changes: 30 additions & 0 deletions app/syslog/SyslogPipelineSd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* See SyslogPipelineSd.h. */

#include "SyslogPipelineSd.h"

#include "SyslogEnterprise.h"

#include "SolidSyslogSdElement.h"
#include "SolidSyslogSdValue.h"
#include "SolidSyslogStructuredDataDefinition.h"

/* A non-zero enterprise number is what makes the SD-ID private: _Begin emits
* "name@number" for one, a bare IANA "name" for 0. */
static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element)
{
(void) base;

SolidSyslogSdElement_Begin(element, "logPipeline", SYSLOG_ENTERPRISE_NUMBER);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), "tls");
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), "hmac-sha256");
SolidSyslogSdElement_End(element);
}

/* No _Create and no pool slot: the library never allocates an SD source, so a
* stateless one is a vtable this application owns. */
static struct SolidSyslogStructuredData s_pipelineSd = {SyslogPipelineSd_Format};

struct SolidSyslogStructuredData* SyslogPipelineSd_Get(void)
{
return &s_pipelineSd;
}
12 changes: 12 additions & 0 deletions app/syslog/SyslogPipelineSd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* A private enterprise SD-ELEMENT, and a worked example of writing one. RFC 5424
* reserves this form for definitions of your own. This element reports which
* transport carried the record and which policy protected it at rest. */
#ifndef APP_SYSLOG_PIPELINE_SD_H
#define APP_SYSLOG_PIPELINE_SD_H

struct SolidSyslogStructuredData;

/** The shared instance, for SolidSyslogConfig.Sd. Stateless, so never NULL. */
struct SolidSyslogStructuredData* SyslogPipelineSd_Get(void);

#endif /* APP_SYSLOG_PIPELINE_SD_H */
13 changes: 13 additions & 0 deletions measurements/pipeline-sd.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# pipeline-sd figures (bytes) — captured by scripts/run.sh (CAPTURE=1).
# The device reads measurements/Baseline.csv as its frozen baseline and reports current-minus-Baseline.
flash_text,363216
flash_data,648
static_bss,148020
heap_used,4440
mbedtls_peak,36104
mbedtls_free,18168
lwip_mem_free,7576
lwip_pbufs_free,14
stack_log,800
stack_service,3820
stack_harness,2848
1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ origin Origin the device named in the record itself, not inferred from the sourc
origin-ip Origin address the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite
tls TLS a collector the device authenticates, and records no longer readable on the wire
hmac HMAC at rest stored records that cannot be edited undetected, not merely checked for corruption
pipeline-sd Private SD-ELEMENT a record that states the protection its own log pipeline was under
28 changes: 14 additions & 14 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# solid-syslog-example — run (hmac)
# solid-syslog-example — run (pipeline-sd)

## Device (self-measured)

Expand All @@ -10,12 +10,12 @@
[device] first record logged: yes
[report] --- SolidSyslog cost above baseline (simulated existing application) ---
[report] key,current,baseline,used_above_baseline
[report] flash_text,363080,349992,13088
[report] flash_data,644,316,328
[report] flash_text,363216,349992,13224
[report] flash_data,648,316,332
[report] static_bss,148020,110876,37144
[report] heap_used,4440,4440,0
[report] mbedtls_peak,36008,21332,14676
[report] mbedtls_free,18264,11436,6828
[report] mbedtls_peak,36080,21332,14748
[report] mbedtls_free,18192,11436,6756
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,14,-1
[report] stack_log,800,120,680
Expand All @@ -29,7 +29,7 @@

```text
text data bss dec hex filename
363072 652 148020 511744 7cf00 /w/build/baseline-cross/baseline.elf
363208 656 148020 511884 7cf8c /w/build/baseline-cross/baseline.elf
```

## Listeners (proved before the device ran)
Expand All @@ -47,21 +47,21 @@
## Collector (syslog-ng) received

```text
wire <134>1 2026-08-16T11:20:29.430000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T11:20:29+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started
wire <134>1 2026-08-16T11:35:22.510000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="251"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="tls" atRest="hmac-sha256"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T11:35:22+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="251"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="tls" atRest="hmac-sha256"] MSG=device started
```

## Self-check (vs measurements/hmac.csv)
## Self-check (vs measurements/pipeline-sd.csv)

```text
OK flash_text: 363080 (expected 363080, Δ0)
OK flash_data: 644 (expected 644, Δ0)
OK flash_text: 363216 (expected 363216, Δ0)
OK flash_data: 648 (expected 648, Δ0)
OK static_bss: 148020 (expected 148020, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 36008 (expected 36056, Δ48)
OK mbedtls_free: 18264 (expected 18216, Δ48)
OK mbedtls_peak: 36080 (expected 36104, Δ24)
OK mbedtls_free: 18192 (expected 18168, Δ24)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 13, Δ0)
OK lwip_pbufs_free: 13 (expected 14, Δ1)
OK stack_log: 800 (expected 800, Δ0)
OK stack_service: 3820 (expected 3820, Δ0)
OK stack_harness: 2848 (expected 2848, Δ0)
Expand Down
Loading