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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ UPSTREAM_INCLUDES := \
# compiles; -MMD tracks them properly from then on.
$(APP_OBJS) $(UPSTREAM_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): | $(FATFS_STAGED_HEADERS)

$(APP_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(APP_WARNINGS) $(APP_INCLUDES) $(MBEDTLS_USER_CONFIG)
$(APP_OBJS) $(SOLIDSYSLOG_PLATFORM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(APP_WARNINGS) $(APP_INCLUDES) $(MBEDTLS_USER_CONFIG) $(SOLIDSYSLOG_USER_TUNABLES)
$(UPSTREAM_OBJS): CFLAGS := $(COMMON_CFLAGS) $(UPSTREAM_WARNINGS) $(UPSTREAM_INCLUDES)

# Our objects, the upstream ones and the platform packs link in loose; mbedTLS
Expand Down
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,46 @@ 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 — AES-GCM at rest
## This stage — Right-sized

Replace the HMAC policy with authenticated encryption. Tamper-evidence establishes that a stored
record was not altered; it does nothing to stop anyone reading it. AES-256-GCM encrypts the body,
authenticates the record header as associated data, and puts the nonce and tag in the trailer.
Fit the compile-time sizes to what this device uses, now that every collaborator is in place.

The message cap comes first, because the ring, the store's record buffer and the formatter frame on
both task stacks all follow it.

```c
struct SolidSyslogMbedTlsAesGcmPolicyConfig gcmConfig = {.GetKey = SyslogStoreKey, .Rng = rng};
/* app/config/solid_syslog_tunables.h */
#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 400U

.SecurityPolicy = SolidSyslogMbedTlsAesGcmPolicy_Create(&gcmConfig),
#define SOLIDSYSLOG_ADDRESS_POOL_SIZE 1U
#define SOLIDSYSLOG_TCP_STREAM_POOL_SIZE 1U
#define SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE 1U
```

GCM needs a fresh nonce per record and mbedTLS has no context-free RNG, so the policy takes the
device's DRBG as well as the key. That is the only wiring difference from the HMAC policy.
The worst case measured here is 345 octets: the four SD-ELEMENTs with both counters at full 32-bit
width and both addresses at fifteen characters, plus a short message. 400 allows for longer messages
on this device. Anything longer is truncated rather than dropped.

The store key does not change. Its name states what it protects rather than which algorithm protects
it, so escalating the policy needs no new key provisioned.
The pool defaults suit a device running several transports at once. This one runs a single sender
over a single stream to a single destination.

These are separate decisions and the second does not follow from the first. A device that only needs
to prove records were not altered can stop at the HMAC.
The overrides reach the library through `SOLIDSYSLOG_USER_TUNABLES_FILE`, an absolute path quoted
for the preprocessor and given to every group that includes a SolidSyslog header: Core, the platform
sources and this application. They change struct sizes, and a build where only some translation
units saw them would disagree about how big those structs are.

The pipeline element now derives both of its values from what the device holds, and each falls back
to the weakest honest answer when the credential behind it is missing:
The ring drops from eight records to four. The store holds a backlog, so the ring only has to absorb
what can be logged while the service task is sending.

```c
s_sd[3] = SyslogPipelineSd_Init(
((clientChain != NULL) && (clientKey != NULL)) ? "mtls" : "tls", (rng != NULL) ? "aes-256-gcm" : "none"
);
```

```text
... [logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started
```
The task stacks go last, at twice their measured high-water marks rounded up to a whole
`configMINIMAL_STACK_SIZE`.

**When you need it.** If a disk that leaves the device would give something away — records naming
users, addresses, process values, or anything else you would not publish.
**When you need it.** Once the pipeline is complete. Sizing earlier means sizing against a device
that is still missing collaborators.

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

**Cost above baseline: Flash +13,788 B, RAM +39,536 B.**
**Cost above baseline: Flash +13,788 B, RAM +36,048 B.**

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

Expand Down Expand Up @@ -80,6 +80,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
| Private SD-ELEMENT | a record that states the protection its own log pipeline was under | +13,556 | +37,472 |
| Mutual TLS | a collector that knows which device sent the record, not just that one did | +13,624 | +39,524 |
| AES-GCM at rest | spooled records unreadable to anyone holding the disk, not just unforgeable | +13,788 | +39,536 |
| Right-sized | the pools, the ring and the task stacks fitted to what this device uses | +13,788 | +36,048 |

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

Expand Down
11 changes: 5 additions & 6 deletions app/AppConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
/* CMSDK UART0 on the mps2-an385, surfaced by QEMU over -serial stdio. */
#define DEVICE_UART0_BASE ((uintptr_t) 0x40004000U)

/* Both seams handle a whole record — the log seam formats one, the service seam
* drains one — so each holds SOLIDSYSLOG_MAX_MESSAGE_SIZE and its onward call
* beneath, and neither fits the FreeRTOS floor. Sized generously here and
* tightened against measured high-water marks once the pipeline is complete. */
#define LOG_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 4U)
#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 16U)
/* Twice the measured high-water mark, rounded up to a whole configMINIMAL_STACK_SIZE,
* or the FreeRTOS floor where that is below it. The reported figure is high-water
* usage, which does not depend on the allocation. */
#define LOG_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 3U)
#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 15U)
#define LOG_TASK_PRIORITY (tskIDLE_PRIORITY + 1U)
#define SERVICE_TASK_PRIORITY (tskIDLE_PRIORITY + 1U)

Expand Down
18 changes: 18 additions & 0 deletions app/config/solid_syslog_tunables.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* SolidSyslog compile-time overrides for this device. Reached via
* SOLIDSYSLOG_USER_TUNABLES_FILE; anything not set here keeps the library
* default from SolidSyslogTunablesDefaults.h. */
#ifndef SOLID_SYSLOG_TUNABLES_H
#define SOLID_SYSLOG_TUNABLES_H

/* The worst case measured here is 345 octets: the structured data at full width
* with a short message. 400 allows for longer messages on this device. Anything
* longer is truncated rather than dropped. */
#define SOLIDSYSLOG_MAX_MESSAGE_SIZE 400U

/* One sender, over one stream, to one destination. The defaults suit a device
* running several transports at once. */
#define SOLIDSYSLOG_ADDRESS_POOL_SIZE 1U
#define SOLIDSYSLOG_TCP_STREAM_POOL_SIZE 1U
#define SOLIDSYSLOG_STREAM_SENDER_POOL_SIZE 1U

#endif /* SOLID_SYSLOG_TUNABLES_H */
6 changes: 3 additions & 3 deletions app/syslog/Syslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
#define SYSLOG_COLLECTOR_HOST "10.0.2.2"
#define SYSLOG_COLLECTOR_PORT ((uint16_t) 6515U)

/* Depth enough to absorb a burst while the sender is busy, without sizing for a
* backlog the store is there to hold. */
#define SYSLOG_BUFFER_RECORDS 8U
/* Enough to absorb a burst logged while the service task is sending. The store
* holds a backlog, so the ring does not have to. */
#define SYSLOG_BUFFER_RECORDS 4U

/* One "<prefix>NN.log" per block, on the volume the device already mounts. */
#define SYSLOG_STORE_PREFIX "syslog"
Expand Down
9 changes: 8 additions & 1 deletion make/solidsyslog.mk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ SOLIDSYSLOG_LIB := $(BUILD)/libSolidSyslog.a
SOLIDSYSLOG_CORE_OBJS := $(SOLIDSYSLOG_CORE_SRCS:%.c=$(OBJ_DIR)/%.o)
SOLIDSYSLOG_PLATFORM_OBJS := $(SOLIDSYSLOG_PLATFORM_SRCS:%.c=$(OBJ_DIR)/%.o)

$(SOLIDSYSLOG_CORE_OBJS): CFLAGS := $(COMMON_CFLAGS) $(SOLIDSYSLOG_CORE_INCLUDES)
# Consumed via #include, so the value has to reach the compiler as a quoted C
# string literal — escaped rather than single-quoted, and absolute, because Core
# compiles without our include path on it.
SOLIDSYSLOG_USER_TUNABLES := -DSOLIDSYSLOG_USER_TUNABLES_FILE=\"$(CURDIR)/$(APP_DIR)/config/solid_syslog_tunables.h\"

# Every object that includes a SolidSyslog header needs it: the tunables change
# struct sizes, so Core, the platforms and our own code must agree on them.
$(SOLIDSYSLOG_CORE_OBJS): CFLAGS := $(COMMON_CFLAGS) $(SOLIDSYSLOG_CORE_INCLUDES) $(SOLIDSYSLOG_USER_TUNABLES)

$(SOLIDSYSLOG_LIB): $(SOLIDSYSLOG_CORE_OBJS)
@mkdir -p $(@D)
Expand Down
13 changes: 13 additions & 0 deletions measurements/right-size.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# right-size 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,363256
flash_data,656
static_bss,146584
heap_used,4440
mbedtls_peak,37244
mbedtls_free,19076
lwip_mem_free,7576
lwip_pbufs_free,13
stack_log,720
stack_service,3740
stack_harness,2848
1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ hmac HMAC at rest stored records that cannot be edited undetected, not merely ch
pipeline-sd Private SD-ELEMENT a record that states the protection its own log pipeline was under
mtls Mutual TLS a collector that knows which device sent the record, not just that one did
aes-gcm AES-GCM at rest spooled records unreadable to anyone holding the disk, not just unforgeable
right-size Right-sized the pools, the ring and the task stacks fitted to what this device uses
34 changes: 17 additions & 17 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# solid-syslog-example — run (aes-gcm)
# solid-syslog-example — run (right-size)

## Device (self-measured)

Expand All @@ -12,14 +12,14 @@
[report] key,current,baseline,used_above_baseline
[report] flash_text,363256,349808,13448
[report] flash_data,656,316,340
[report] static_bss,150072,110876,39196
[report] static_bss,146584,110876,35708
[report] heap_used,4440,4440,0
[report] mbedtls_peak,37136,21328,15808
[report] mbedtls_free,19184,11440,7744
[report] mbedtls_peak,37260,21328,15932
[report] mbedtls_free,19060,11440,7620
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,13,0
[report] stack_log,800,120,680
[report] stack_service,3820,52,3768
[report] lwip_pbufs_free,14,13,1
[report] stack_log,720,120,600
[report] stack_service,3740,52,3688
[report] stack_harness,2848,2840,8
[report] --- end ---
[device] ready
Expand All @@ -29,7 +29,7 @@

```text
text data bss dec hex filename
363248 664 150072 513984 7d7c0 /w/build/baseline.elf
363248 664 146584 510496 7ca20 /w/build/baseline.elf
```

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

```text
wire <134>1 2026-08-16T19:53:56.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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T19:53:56+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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] MSG=device started
wire <134>1 2026-08-16T19:57:02.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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T19:57:02+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"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] MSG=device started
```

## Self-check (vs measurements/aes-gcm.csv)
## Self-check (vs measurements/right-size.csv)

```text
OK flash_text: 363256 (expected 363256, Δ0)
OK flash_data: 656 (expected 656, Δ0)
OK static_bss: 150072 (expected 150072, Δ0)
OK static_bss: 146584 (expected 146584, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 37136 (expected 37248, Δ112)
OK mbedtls_free: 19184 (expected 19072, Δ112)
OK mbedtls_peak: 37260 (expected 37244, Δ16)
OK mbedtls_free: 19060 (expected 19076, Δ16)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 13, Δ0)
OK stack_log: 800 (expected 800, Δ0)
OK stack_service: 3820 (expected 3820, Δ0)
OK lwip_pbufs_free: 14 (expected 13, Δ1)
OK stack_log: 720 (expected 720, Δ0)
OK stack_service: 3740 (expected 3740, Δ0)
OK stack_harness: 2848 (expected 2848, Δ0)
```

Expand Down