CXL · Module 19
Secure Communication
Authentication established who is on the link. This chapter protects what crosses it: integrity against confidentiality, MAC strength, replay counters, key lifetime, IV uniqueness, the bandwidth overhead, selective protection, downgrade negotiation and verification failure.
19.1 established who is on the other end of the link. It said nothing about what happens to the traffic afterwards.
An authenticated device on an unprotected link is a device whose identity is known and whose flits anybody on the path can read, modify or replay. This chapter is the other half.
1. The Engineering Problem — Encryption Is Not Protection
Six things separate a protected link from an encrypted one.
Integrity and confidentiality are separate properties, and only one of them stops an attacker changing what you receive. A link with encryption and no MAC accepts modified ciphertext without noticing. Section 5.
A MAC is a probability. Its length decides how many forgeries an attacker expects to try, and a truncated MAC is a work factor somebody can reach. Section 6.
Replay needs a counter. A flit that verifies perfectly and has been seen before is a recording, and a receiver with no sequence check cannot distinguish it from a new one. Section 7.
A key has a lifetime measured in data and in sequence numbers, and rekeying must happen before either runs out — because a wrapped counter makes an old flit look new. Section 9.
An initialisation vector must never repeat under one key. Reuse is not a weakness of the cipher; it is a failure of the counter that feeds it. Section 10.
And negotiation must not be able to select nothing. A downgrade an attacker can force is a downgrade an attacker will force. Section 13.
This chapter against 19.1, stated precisely. That one owns establishing identity, once, at attach. This one owns protecting every flit afterwards — and section 15 shows that the first is a property of the second rather than an alternative to it.
2. The One-Sentence Model
A secure link knows who is at the other end, detects modification, rejects repetition, and uses keys that have not run out — and every defect below is a link with encryption and one of those four missing.
3. What This Chapter Owns
| Ground | Owner |
|---|---|
| Establishing which device this is | 19.1 |
| Isolating hosts and tenants that share a pool | 19.3 |
| Policy enforcement across tenants | 19.4 |
| The bandwidth ceiling the overhead eats into | 18.2 |
| Protecting the traffic on an established link | this chapter |
Deferred:
| Deferred ground | Owner |
|---|---|
| Cryptographic primitives themselves | out of scope — see §4 |
| Isolation between tenants on one device | 19.3 |
| Key provisioning and distribution | out of scope — see §20 |
| Side channels and traffic analysis beyond §12 | out of scope |
4. Teaching-Model Boundary
Sixteen-bit sequence numbers, a four-way traffic classification and a three-level negotiation are far smaller than any real deployment. They are sized so every boundary is reachable and every result checkable by hand.
What is not simplified is the structure: integrity and confidentiality as independent properties, a work factor derived from MAC length, a monotonic counter with an explicit wrap condition, a key lifetime bounded by two separate limits, an IV compared within a key epoch, and a negotiated level compared against a policy floor.
Three things are absent by design. Cryptography is out of scope — a chapter that modelled AES badly would be worse than one that treats it as an input. Key provisioning is out of scope: section 9 says when to rekey and nothing about how the new key is established. And traffic analysis beyond section 12 is out of scope — the model shows that unprotected addresses leak the access pattern and does not attempt to quantify what an observer learns.
5. RTL 1 — Integrity And Confidentiality Are Different
// Integrity and confidentiality are separate properties. Protecting one says
// nothing about the other.
module protection_class #(parameter int ENCRYPT_ONLY = 0) (
input logic clk, rst_n,
input logic send,
input logic mac_present, cipher_on,
output logic integrity_ok, confidential, both,
output logic [7:0] n_sent, n_unprotected,
output logic tamper_blind_err
);
// A MAC detects modification. Encryption hides content. Neither implies the
// other, and a link with only encryption accepts modified ciphertext.
assign integrity_ok = (ENCRYPT_ONLY != 0) ? 1'b0 : mac_present;
assign confidential = cipher_on;
assign both = integrity_ok && confidential;
// Sending on a link that cannot detect modification.
assign tamper_blind_err = send && !integrity_ok;
// ... send counters omitted for length
endmoduleThree configurations:
protect: sends=3 unprotected=2 | encrypt-only tamper-blind=3| Configuration | What it gives, and what it misses |
|---|---|
| MAC and cipher | integrity and confidentiality — not tamper-blind |
| MAC, no cipher | integrity, no confidentiality — not tamper-blind |
| Cipher, no MAC | confidentiality, no integrity — tamper-blind |
The second and third rows are the whole argument. A MAC without a cipher is a real and useful configuration — the traffic is readable and cannot be modified undetected. A cipher without a MAC is a link where an attacker cannot read the traffic and can still change it, and the receiver will decrypt whatever arrives and act on the result.
6. RTL 2 — A MAC Is A Probability
// A MAC is a probability, not a guarantee. Its length decides how many forgeries
// an attacker expects to try.
module mac_strength (
input logic clk, rst_n,
input logic evaluate,
input logic [7:0] mac_bits,
input logic [15:0] attempts_k,
output logic [31:0] work_log2, expected_k,
output logic adequate, truncation_err
);
// Forging a MAC of n bits takes 2^(n-1) expected attempts. Carrying the
// exponent rather than the value keeps the arithmetic in range.
assign work_log2 = (mac_bits == 8'd0) ? 32'd0 : ({24'd0, mac_bits} - 32'd1);
assign expected_k = {16'd0, attempts_k};
assign adequate = (work_log2 >= 32'd63);
// A MAC short enough that the stated attempt budget can forge one.
assign truncation_err = evaluate && (work_log2 < 32'd32);
endmodule mac: bits=64 work=2^63 adequate=1 truncated=0| MAC length | Expected forgery work and Verdict |
|---|---|
| 64 bits | 2^63 · adequate |
| 63 bits | 2^62 · just short |
| 33 bits | 2^32 · at the truncation boundary, inside it |
| 32 bits | 2^31 · truncated — reachable |
| 0 bits | 2^0 · no MAC at all |
The work factor is carried as an exponent rather than a value, because 2^63 does not fit anywhere sensible and the comparison only ever needs the exponent. That is a modelling choice worth naming: a quantity that only appears inside comparisons does not need to be materialised.
The truncation threshold at 2^32 is not arbitrary — it is roughly the point at which a determined attacker's forgery budget becomes a number rather than a fantasy. A truncated MAC does not fail; it becomes a thing somebody can afford to attack, and the difference between 32 bits and 64 is the difference between a budget and an impossibility.
7. RTL 3 — Replay Needs A Counter
// A replay counter must be monotonic, and a receiver must reject anything it has
// already seen.
module replay_counter #(parameter int ACCEPT_ANY = 0) (
input logic clk, rst_n,
input logic rx,
input logic [15:0] seq_in,
output logic [15:0] highest_seen,
output logic accepted, is_replay, wrapped,
output logic [7:0] n_rx, n_dropped,
output logic replay_accept_err
);
assign is_replay = (seq_in <= highest_seen);
// Wrapping the counter makes an old sequence number look new, which is why a
// rekey has to happen before the counter can wrap.
assign wrapped = (highest_seen == 16'hFFFF);
// The accepting build takes anything, which is a link with no replay
// protection at all however good its MAC is.
assign accepted = (ACCEPT_ANY != 0) ? 1'b1 : !is_replay;
// Accepting a sequence number that has already been used.
assign replay_accept_err = rx && accepted && is_replay;
// ... receive counters omitted for length
endmoduleFive flits:
replay: rx=5 dropped=2 highest=50 | accepting build accepted=2| Sequence | Highest seen and Verdict |
|---|---|
| 1 | 0 · accept |
| 2 | 1 · accept |
| 2 again | 2 · replay — drop |
| 1 | 2 · replay — drop |
| 50 — a jump forward | 2 · accept |
A gap is not a replay. Sequence 50 after sequence 2 means 47 flits were lost, which is a reliability question rather than a security one — and a receiver that rejected gaps would reject every lossy link. The comparison is <= against the highest seen, not != highest + 1.
wrapped exists to connect this model to section 9. A sixteen-bit counter at FFFF has nowhere to go: the next flit either stalls the link or wraps to zero, and a wrapped counter makes every old flit look new again. That is why the rekey in section 9 is bounded by sequence space as well as by bytes.
8. Waveform — A Replay Arriving On A Protected Link
Transcribed from the printed trace. One stimulus stream, both builds.
The mac_ok row being flat at 1 is the point. A replayed flit is a genuine flit that was genuinely sent by the genuine device — its MAC verifies because it is a real MAC over real data. Integrity checking cannot detect a replay, and that is why the counter is a separate mechanism rather than a property of the MAC.
9. RTL 4 — A Key Has A Lifetime
// A key has a lifetime measured in the data it protects, and rekeying must
// happen before the counter that guarantees uniqueness runs out.
module key_lifetime #(parameter int NEVER_REKEY = 0) (
input logic clk, rst_n,
input logic transmit,
input logic [31:0] bytes_sent, byte_budget,
input logic [15:0] seq_now, seq_max,
output logic budget_spent, seq_exhausted, must_rekey,
output logic [7:0] n_tx, n_rekeys,
output logic overrun_err
);
assign budget_spent = (bytes_sent >= byte_budget);
assign seq_exhausted = (seq_now >= seq_max);
// Either limit forces a rekey. The never-rekey build keeps going, which
// eventually reuses a sequence number under the same key.
assign must_rekey = (NEVER_REKEY != 0) ? 1'b0 : (budget_spent || seq_exhausted);
// Transmitting past a limit that should have forced a rekey.
assign overrun_err = transmit && (budget_spent || seq_exhausted) && !must_rekey;
// ... transmit counters omitted for length
endmodule key: tx=5 rekeys=2 | never-rekey overruns=2Two independent limits, and either one alone forces a rekey:
| Bytes sent | Sequence and Rekey required |
|---|---|
| 1,000 of 100,000 | 10 of 1000 · no |
| exactly 100,000 | 10 · yes — the byte budget |
| 99,999 | 10 · no — one byte short |
| 1,000 | exactly 1000 · yes — the sequence space |
| 1,000 | 999 · no — one short |
The bench drives each limit alone and each boundary exactly, because the two mutations that reduce must_rekey to one term or the other are killed by different rows.
The sequence limit is the one that surprises people. A link nowhere near its data budget can still exhaust a sixteen-bit sequence counter, and section 7's wrapped output is what happens next: the counter returns to zero, every old flit becomes acceptable again, and the replay protection built so carefully in section 7 evaporates.
The absence of an edge out of WRAPPED is the point. There is no recovery: the sequence space has already been reused under the key, the IVs have already repeated, and no amount of later care undoes flits that were accepted in the meantime. The rekey is not a maintenance operation — it is the only transition that keeps the state machine out of a terminal state, and section 9's two limits exist to force it before the edge to WRAPPED is taken.
10. RTL 5 — An IV Must Never Repeat Under One Key
// An initialisation vector must never repeat under one key. Reuse is not a
// weakness of the cipher; it is a failure of the counter that feeds it.
module iv_uniqueness #(parameter int REUSE_IV = 0) (
input logic clk, rst_n,
input logic encrypt,
input logic [15:0] iv_in, key_epoch, last_iv, last_epoch,
output logic [15:0] iv_used,
output logic unique_iv, same_key,
output logic [7:0] n_encrypt, n_reused,
output logic iv_reuse_err
);
assign same_key = (key_epoch == last_epoch);
// A repeated IV is only a problem under the same key: a rekey resets the space.
assign iv_used = (REUSE_IV != 0) ? 16'h00FF : iv_in;
assign unique_iv = !same_key || (iv_used != last_iv);
// Encrypting with an initialisation vector already used under this key.
assign iv_reuse_err = encrypt && !unique_iv;
// ... encryption counters omitted for length
endmodule iv: encryptions=5 reused=1 | reusing build reused=2The !same_key term is what makes this model correct rather than merely strict. An IV repeated under a different key is not a reuse — a rekey resets the IV space entirely, and a model that forbade repetition across epochs would forbid the only mechanism that makes a finite IV space workable:
| This encryption | Against the last one, and the verdict |
|---|---|
| IV 1, epoch 1 | last IV 0, same epoch — unique |
| IV 1, epoch 1 | last IV 1, same epoch — a reuse |
| IV 1, epoch 2 | last IV 1, different epoch — unique again |
| 1 | 2 · 1 · 1 · yes — different key |
This is the same structure as section 9's sequence limit seen from the other side: the rekey is what makes both counters finite and safe. Without it, the IV space and the sequence space are both finite and both eventually repeat.
11. RTL 6 — Security Costs Bandwidth
// Security costs bandwidth. The MAC is bytes on the wire that carry no payload,
// and 18.2's ceiling applies to the total.
module security_overhead #(parameter int IGNORE_OVERHEAD = 0) (
input logic clk, rst_n,
input logic send,
input logic [15:0] payload_b, mac_b, iv_b, flit_b,
output logic [31:0] on_wire_b,
output logic [7:0] overhead_pct, efficiency_pct,
output logic [15:0] effective_gbps,
input logic [15:0] raw_gbps,
output logic overhead_blind_err
);
logic [31:0] ov_q, ef_q, eg_q;
// Every protected flit carries a MAC and an IV alongside its payload.
assign on_wire_b = (IGNORE_OVERHEAD != 0) ? {16'd0, payload_b}
: ({16'd0, payload_b} + {16'd0, mac_b} + {16'd0, iv_b});
// A build that does not count the MAC and IV on the wire cannot report their
// share either: it has no way to know they are there.
assign ov_q = (IGNORE_OVERHEAD != 0) ? 32'd0
: ((on_wire_b == 32'd0) ? 32'd0
: ((({16'd0, mac_b} + {16'd0, iv_b}) * 32'd100) / on_wire_b));
// ... efficiency and effective rate omitted for length
// Reporting no overhead on a link that is carrying a MAC.
assign overhead_blind_err = send && (mac_b != 16'd0) && (overhead_pct == 8'd0);
endmoduleA 16-byte MAC and an 8-byte IV on a 400 Gbps link:
| Payload | On the wire, overhead, and payload delivered |
|---|---|
| 8 bytes | 32 on the wire · 75% overhead · 100 Gbps delivered |
| 64 bytes | 88 on the wire · 27% overhead · 288 Gbps delivered |
| 256 bytes | 280 on the wire · 8% overhead · 364 Gbps delivered |
overhead: on_wire=88B overhead=27% effective=288Gbps | ignoring blind=3A 400 Gbps link protecting 64-byte payloads delivers 288 Gbps of payload. The other 112 is MAC and IV, and 18.2's entire ceiling analysis applies to the 400, not the 288 — which means security overhead sits alongside flit efficiency as a second multiplier on the same raw rate.
This is 18.3 section 13's small-access argument in a third setting. At an 8-byte payload the security overhead is 75% — three bytes of protection for every byte protected — which is a strong argument for batching that has nothing to do with the API cost that chapter modelled.
The no-MAC case is driven and is not blindness: a link genuinely carrying no MAC has genuinely no overhead, and a checker that fired there would fire on every unprotected link.
12. RTL 7 — What Is Left In The Clear
// Protecting some traffic and not the rest. What is left in the clear is a
// policy decision, and the metadata usually is.
module selective_protection #(parameter int PROTECT_DATA_ONLY = 0) (
input logic clk, rst_n,
input logic send,
input logic [1:0] traffic, // 0 data, 1 address, 2 control, 3 management
output logic protected_now,
output logic [3:0] exposed_mask,
output logic [7:0] n_sent, n_clear,
output logic metadata_leak_err
);
// Protecting payloads and leaving addresses in the clear leaks the access
// pattern, which for a memory device is most of what an observer wants.
assign protected_now = (PROTECT_DATA_ONLY != 0) ? (traffic == 2'd0) : 1'b1;
assign exposed_mask[0] = (PROTECT_DATA_ONLY != 0) ? 1'b0 : 1'b0;
assign exposed_mask[1] = (PROTECT_DATA_ONLY != 0) ? 1'b1 : 1'b0; // addresses
assign exposed_mask[2] = (PROTECT_DATA_ONLY != 0) ? 1'b1 : 1'b0; // control
assign exposed_mask[3] = (PROTECT_DATA_ONLY != 0) ? 1'b1 : 1'b0; // management
// Sending address or control traffic in the clear.
assign metadata_leak_err = send && (traffic != 2'd0) && !protected_now;
// ... send counters omitted for length
endmodule selective: sends=4 clear=0 | data-only clear=3 leaks=3The data-only build sends three of four traffic classes in the clear, and for a memory device those three are most of what an observer wants:
| Traffic class | What it reveals if unprotected |
|---|---|
| Data | the contents — which is what everybody protects |
| Addresses | the access pattern — which pages, in what order, how often |
| Control | the transaction mix and the protocol in use |
| Management | the fabric's own configuration traffic |
The address stream is the sharpest of the three. For a CXL memory device, the sequence of addresses is a very good description of what the workload is doing — 18.3 built four chapters' worth of inference from exactly that signal, and an observer with the address stream has the same material.
exposed_mask names which classes are in the clear rather than reporting a single "partially protected" bit, for the reason every failure mask in this track exists: a policy decision needs to be visible at the granularity it was made.
13. RTL 8 — Negotiation Must Have A Floor
// Negotiation must not be able to select nothing. A downgrade an attacker can
// force is a downgrade an attacker will force.
module security_negotiate #(parameter int ALLOW_NONE = 0) (
input logic clk, rst_n,
input logic negotiate,
input logic [1:0] host_level, dev_level, // 0 none, 1 integrity, 2 integrity+confidentiality
input logic [1:0] policy_floor,
output logic [1:0] agreed_level,
output logic meets_floor, downgraded,
output logic [7:0] n_negotiations, n_refused,
output logic downgrade_err
);
logic [1:0] lower;
assign lower = (host_level < dev_level) ? host_level : dev_level;
// The permissive build accepts whatever the pair can agree on, including
// nothing. The correct build refuses below a configured floor.
assign agreed_level = (ALLOW_NONE != 0) ? lower
: ((lower >= policy_floor) ? lower : 2'd3);
assign meets_floor = (lower >= policy_floor);
assign downgraded = (lower < host_level);
// Agreeing a level below the configured floor.
assign downgrade_err = negotiate && (ALLOW_NONE != 0) && !meets_floor;
// ... negotiation counters omitted for length
endmodule negotiate: negotiations=5 below_floor=2 | permissive downgrades=2| Host and device | What is agreed, against an integrity floor |
|---|---|
| both / both | agrees on both — meets the floor |
| both / integrity only | agrees on integrity — a downgrade, and permitted |
| both / none | refused — below the floor |
A downgrade is not automatically a failure. The second row is a legitimate outcome: the device cannot do confidentiality, the pair agree on integrity, and integrity is above the floor. The distinction the model draws is between a downgrade and a downgrade below what policy allows, and only the second is an error.
The floor is compared inclusively and driven exactly: a level exactly at the floor meets it, and a floor one above the agreed level does not.
The permissive build's failure is that it has no floor at all. It agrees on whatever the two ends can both do, which means a device claiming to support nothing gets a link with nothing — and an attacker who can influence what the device claims gets to choose.
14. RTL 9 — What A Failed Verification Means
// What a receiver does with a flit whose MAC does not verify. Dropping silently
// and reporting are different, and only one of them is detectable.
module verify_failure #(parameter int SILENT_DROP = 0) (
input logic clk, rst_n,
input logic rx,
input logic mac_ok,
input logic [15:0] fails_seen, alarm_threshold,
output logic deliver, dropped, alarm,
output logic [7:0] n_rx, n_dropped,
output logic silent_err
);
assign deliver = mac_ok;
assign dropped = !mac_ok;
// A run of verification failures is an attack signature. Dropping without
// counting makes it indistinguishable from a noisy link.
assign alarm = (SILENT_DROP != 0) ? 1'b0 : (fails_seen >= alarm_threshold);
// Dropping a flit that failed verification without raising anything.
assign silent_err = rx && dropped && (fails_seen >= alarm_threshold) && !alarm;
// ... receive counters omitted for length
endmodule verify: rx=5 dropped=3 | silent build silent drops=1One verification failure is a bit error. A hundred is an attack. The individual response is the same either way — drop the flit — and the difference is entirely in whether anybody counts:
| Failures seen | Alarm |
|---|---|
| 1 | no — indistinguishable from line noise |
| 7 | no — one below the threshold |
| 8 | yes — a signature |
The threshold is inclusive and driven exactly. silent_err requires the flit to have been dropped — a verifying flit arriving during a run of failures is delivered normally and is not a silent drop, and the bench drives that case to prove the term earns its place.
A link that drops silently reports perfect health. Every flit that fails verification disappears, the counters that would show it do not exist, and an attacker probing the MAC gets unlimited attempts against section 6's work factor with nobody watching.
15. RTL 10 — The Secure Link Assembled
// Link security assembled: every property a protected link needs before the
// traffic on it can be relied on.
module secure_link #(parameter int ENCRYPTION_ONLY = 0) (
input logic clk, rst_n,
input logic evaluate,
input logic peer_authenticated, // 19.1 completed
input logic integrity_present, // a MAC over every flit
input logic replay_protected, // a monotonic counter
input logic key_fresh, // within its byte and sequence budget
input logic floor_met, // negotiation did not go below policy
output logic link_secure,
output logic [4:0] fail_mask,
output logic [7:0] n_eval, n_secure,
output logic false_security_err
);
assign fail_mask[0] = ~peer_authenticated;
assign fail_mask[1] = ~integrity_present;
assign fail_mask[2] = ~replay_protected;
assign fail_mask[3] = ~key_fresh;
assign fail_mask[4] = ~floor_met;
// The encryption-only build calls a link secure because the traffic is
// unreadable, which says nothing about who is at the other end or whether the
// ciphertext has been modified or replayed.
assign link_secure = (ENCRYPTION_ONLY != 0) ? 1'b1 : (fail_mask == 5'd0);
assign false_security_err = evaluate && link_secure && (fail_mask != 5'd0);
// ... evaluation counters omitted for length
endmodule link: evaluated=6 secure=1 | encryption-only secure=6One secure link out of six, and the encryption-only build called all six secure. It is the widest gap of any assembled model in this batch, and the reason is that its criterion is not one of the five properties at all — it is "the traffic is unreadable", which none of the five gates measures:
| Property | The question it answers and Encryption-only |
|---|---|
| Peer authenticated | who is at the other end? — 19.1 · missed |
| Integrity present | has this been modified? · missed |
| Replay protected | have I seen this before? · missed |
| Key fresh | is the key still safe to use? · missed |
| Floor met | did negotiation give away too much? · missed |
The first row is where this chapter and 19.1 join. A perfectly protected link to an unauthenticated peer is a channel whose confidentiality and integrity are established with somebody — and the whole of 19.1 is establishing which somebody. Neither chapter is useful alone.
16. Quantitative Reasoning
Every number is from a printed line above. None describes any implementation.
Protection classes. Three configurations, two lacking both properties — and the encrypt-only build was tamper-blind on all three.
MAC strength. 64 bits is 2^63 expected forgeries; 32 bits is 2^31 and reported as a truncation.
Replay. Five flits, two dropped as replays — both with valid MACs. A jump from 2 to 50 is a gap and is accepted.
Key lifetime. Five transmissions, two rekeys — one forced by the byte budget and one by the sequence space alone.
IV uniqueness. Five encryptions, one reuse; the reusing build repeated its single IV twice while being handed fresh ones.
Overhead. 64 + 16 + 8 = 88 bytes on the wire for 64 useful — 27% overhead, and a 400 Gbps link delivering 288 Gbps of payload. At an 8-byte payload the overhead is 75%.
Selective protection. Four traffic classes; the data-only build sent three in the clear.
Negotiation. Five negotiations, two below the floor — refused by the correct build and agreed by the permissive one.
Verification failure. Five flits, three dropped; the alarm threshold is reached at eight failures and the silent build raises nothing.
Assembled. Six evaluations, one secure link against the encryption-only build's six.
17. Assertions
Every property is an immediate check written as cond !== 1'b1, sampled after a settle. 179 assertion sites across two testbenches.
| # · model | Property |
|---|---|
| 1 · protect | A MAC gives integrity |
| 2 · protect | A cipher gives confidentiality |
| 3 · protect | So the link has both |
| 4 · protect | And is not tamper-blind |
| 5 · protect | The encrypt-only build has no integrity |
| 6 · protect | So it is tamper-blind |
| 7 · protect | A MAC without a cipher still gives integrity |
| 8 · protect | With no confidentiality |
| 9 · protect | So not both |
| 10 · protect | But it is not tamper-blind |
| 11 · protect | No MAC is no integrity |
| 12 · protect | Confidentiality without it |
| 13 · protect | Which is tamper-blind |
| 14 · protect | Three sends |
| 15 · protect | Two of them without both properties |
| 16 · protect | The correct build was tamper-blind once, with no MAC |
| 17 · protect | The encrypt-only build was tamper-blind on all three |
| 18 · mac | A 64-bit MAC is 2^63 expected attempts |
| 19 · mac | Which is adequate |
| 20 · mac | And not a truncation |
| 21 · mac | A 32-bit MAC is 2^31 |
| 22 · mac | Which is not adequate |
| 23 · mac | And is reported as a truncation |
| 24 · mac | A 63-bit MAC is 2^62 |
| 25 · mac | Which is just short |
| 26 · mac | A 33-bit MAC is 2^32 |
| 27 · mac | Which is exactly at the truncation boundary and inside it |
| 28 · mac | A zero-bit MAC is no work at all |
| 29 · mac | And is a truncation |
| 30 · replay | Sequence 1 against a highest of 0 is not a replay |
| 31 · replay | So it is accepted |
| 32 · replay | Sequence 2 is still ahead |
| 33 · replay | And accepted |
| 34 · replay | Sequence 2 again is a replay |
| 35 · replay | So the correct receiver drops it |
| 36 · replay | The accepting build takes it |
| 37 · replay | Which is accepting a replay |
| 38 · replay | And the correct receiver does not |
| 39 · replay | An older sequence is a replay too |
| 40 · replay | And is dropped |
| 41 · replay | A jump forward is not a replay |
| 42 · replay | And is accepted |
| 43 · replay | The highest seen is 50 |
| 44 · replay | Five received |
| 45 · replay | Two dropped as replays |
| 46 · replay | The accepting build counted the same two |
| 47 · replay | The correct receiver never accepts a replay |
| 48 · replay | The accepting build accepted both |
| 49 · key | 1000 bytes of a 100000 budget is not spent |
| 50 · key | And sequence 10 of 1000 is not exhausted |
| 51 · key | So no rekey is needed |
| 52 · key | And nothing has overrun |
| 53 · key | Exactly the budget is spent |
| 54 · key | So a rekey is required |
| 55 · key | The never-rekey build carries on |
| 56 · key | Which is an overrun |
| 57 · key | And the correct build reports none |
| 58 · key | One byte short is not spent |
| 59 · key | And no rekey is needed |
| 60 · key | The byte budget is fine |
| 61 · key | But the sequence space is exhausted |
| 62 · key | Which forces a rekey on its own |
| 63 · key | And the never-rekey build overruns again |
| 64 · key | One short of the maximum is not exhausted |
| 65 · key | Five transmissions |
| 66 · key | Two rekeys required |
| 67 · key | The never-rekey build required none |
| 68 · key | The correct build never overruns |
| 69 · key | The never-rekey build overran twice |
| 70 · iv | The same key epoch |
| 71 · iv | And a new IV is unique |
| 72 · iv | With no reuse |
| 73 · iv | The same IV under the same key is not unique |
| 74 · iv | Which is a reuse |
| 75 · iv | A different key epoch |
| 76 · iv | So the same IV is unique again |
| 77 · iv | With no reuse |
| 78 · iv | The correct build's IV is fresh |
| 79 · iv | The reusing build repeats its own |
| 80 · iv | Which is a reuse |
| 81 · iv | And the correct build reports none |
| 82 · iv | A second fresh IV is still unique |
| 83 · iv | And the reusing build repeats itself again |
| 84 · iv | A second reuse |
| 85 · iv | Five encryptions |
| 86 · iv | One reuse in the correct build |
| 87 · iv | Which it reported |
| 88 · iv | Against the reusing build's two |
| 89 · overhead | 64 plus 16 plus 8 is 88 bytes on the wire |
| 90 · overhead | A 27 percent security overhead |
| 91 · overhead | Leaving 72 percent efficiency |
| 92 · overhead | So a 400Gbps link delivers 288 of payload |
| 93 · overhead | The ignoring build reports 64 bytes |
| 94 · overhead | With zero overhead |
| 95 · overhead | Which is overhead-blind |
| 96 · overhead | And the correct build is not |
| 97 · overhead | 256 plus 24 is 280 bytes |
| 98 · overhead | An 8 percent overhead |
| 99 · overhead | Delivering 364 of payload |
| 100 · overhead | 8 plus 24 is 32 bytes |
| 101 · overhead | A 75 percent overhead |
| 102 · overhead | Delivering 100 of payload on a 400Gbps link |
| 103 · overhead | No MAC and no IV is no overhead |
| 104 · overhead | Which is not blindness |
| 105 · overhead | In either build |
| 106 · overhead | The correct build is never overhead-blind |
| 107 · overhead | The ignoring build was blind on all three protected sends |
| 108 · selective | Data is protected |
| 109 · selective | In both builds |
| 110 · selective | With no leak |
| 111 · selective | The correct build exposes nothing |
| 112 · selective | The data-only build exposes all three metadata classes |
| 113 · selective | Addresses are protected by the correct build |
| 114 · selective | And left in the clear by the data-only build |
| 115 · selective | Which leaks metadata |
| 116 · selective | And the correct build does not |
| 117 · selective | Control traffic is also in the clear |
| 118 · selective | Another leak |
| 119 · selective | And management traffic too |
| 120 · selective | A third |
| 121 · selective | Four sends |
| 122 · selective | The correct build sent nothing in the clear |
| 123 · selective | The data-only build sent three |
| 124 · selective | And the correct build leaked no metadata |
| 125 · selective | Against the data-only build's three |
| 126 · negotiate | Both at the highest level agree on it |
| 127 · negotiate | Which meets the floor |
| 128 · negotiate | With no downgrade |
| 129 · negotiate | And nothing reported |
| 130 · negotiate | A device at integrity only agrees there |
| 131 · negotiate | Which still meets the floor |
| 132 · negotiate | Though it is a downgrade from the host's level |
| 133 · negotiate | And is permitted |
| 134 · negotiate | A device at none does not meet the floor |
| 135 · negotiate | So the correct build refuses |
| 136 · negotiate | The permissive build agrees on nothing |
| 137 · negotiate | Which is a downgrade past the floor |
| 138 · negotiate | And the correct build reports none |
| 139 · negotiate | Exactly at the floor meets it |
| 140 · negotiate | And one above it does not |
| 141 · negotiate | Five negotiations |
| 142 · negotiate | Two below the floor |
| 143 · negotiate | The correct build never agrees below the floor |
| 144 · negotiate | The permissive build did twice |
| 145 · verify | A verifying flit is delivered |
| 146 · verify | And not dropped |
| 147 · verify | With no alarm |
| 148 · verify | And nothing silent |
| 149 · verify | A failing flit is not delivered |
| 150 · verify | It is dropped |
| 151 · verify | One failure is below the alarm threshold |
| 152 · verify | So dropping it silently is correct |
| 153 · verify | Eight failures reaches the threshold |
| 154 · verify | The silent build raises nothing |
| 155 · verify | Which is a silent drop |
| 156 · verify | And the correct build is not silent |
| 157 · verify | Seven is one short |
| 158 · verify | And neither build is silent there |
| 159 · verify | A verifying flit is delivered whatever the history |
| 160 · verify | Though the alarm stands |
| 161 · verify | And it is not a silent drop, because it was not dropped |
| 162 · verify | Five flits received |
| 163 · verify | Three dropped |
| 164 · verify | The correct build never drops silently |
| 165 · verify | The silent build did once |
| 166 · link | All five properties present |
| 167 · link | So the link is secure |
| 168 · link | The peer-authentication property alone is missing |
| 169 · link | So the correct build calls the link insecure |
| 170 · link | The encryption-only build calls it secure |
| 171 · link | Which is false security |
| 172 · link | And the correct build claims none |
| 173 · link | The integrity property alone, also missed |
| 174 · link | The replay property alone, also missed |
| 175 · link | The key-freshness property alone, missed |
| 176 · link | The floor property alone, also missed |
| 177 · link | Six link evaluations |
| 178 · link | One secure link |
| 179 · link | The encryption-only build called all six secure |
18. Mutation Testing
70 mutations, one at a time, each required to make the baseline print RESULT: FAIL.
70 of 70 were killed.
The first run killed 67 and left 3 survivors, of which two were provably equivalent:
| Class | Count, and the fix |
|---|---|
| Unobserved output | 1 · assert exposed_mask |
| Provably equivalent | 2 · replaced |
"Leak reported on unprotected data" could not be killed by any stimulus. protected_now is false only when traffic != 0, so the guard (traffic != 2'd0) in the checker can never change the result — the two expressions are identical for every input. The term is defensive and documents intent, and the mutation was replaced.
"Downgrade reported on an upgrade" was the same shape. lower is the minimum of the host and device levels, so lower <= host_level always holds and lower != host_level and lower < host_level coincide. Replaced with one that measures the downgrade against the device instead.
A representative sample:
| Mutation | Result |
|---|---|
| Integrity without a MAC | KILLED |
| The encrypt-only build gains integrity | KILLED |
| Confidentiality from the MAC | KILLED |
| Either property counts as both | KILLED |
| Tamper blindness judged on the cipher | KILLED |
| The work factor is the MAC length | KILLED |
| The work factor is unguarded at zero | KILLED |
| The adequacy boundary is off by one | KILLED |
| The truncation boundary is off by one | KILLED |
| The replay boundary is off by one | KILLED |
| Nothing is ever a replay | KILLED |
| The highest seen is frozen | KILLED |
| The byte-budget boundary is off by one | KILLED |
| The sequence boundary is off by one | KILLED |
| A rekey only on the byte budget | KILLED |
| A rekey only on the sequence space | KILLED |
| Every epoch is the same key | KILLED |
| Uniqueness ignores the key epoch | KILLED |
| Uniqueness ignores the IV | KILLED |
| The IV is not counted on the wire | KILLED |
| The overhead gating is inverted | KILLED |
| Efficiency inverted | KILLED |
| The effective rate is the raw rate | KILLED |
| Overhead blindness reported with no MAC | KILLED |
| The correct build protects data only | KILLED |
| Addresses never reported exposed | KILLED |
| Control traffic never reported exposed | KILLED |
| The negotiation takes the higher level | KILLED |
| The floor boundary is off by one | KILLED |
| The correct build agrees below the floor | KILLED |
| The alarm boundary is off by one | KILLED |
| A silent drop reported on a delivered flit | KILLED |
| The encryption-only build checks everything | KILLED |
19. Verification Strategy
Two builds, one stimulus. The parameter is the only difference, and the "broken" build is always a receiver that omits a check.
Drive each term of every disjunction alone. Section 9's rekey has two triggers and the two mutations reducing it to either one are killed by different rows.
Drive the case where the shortcut is correct. A link with no MAC, where zero overhead is honest. A verifying flit during a run of failures. A repeated IV after a rekey. In each, a checker that fired would fire on a good case.
Distinguish a gap from a replay. Section 7's jump from 2 to 50 is 47 lost flits, and a receiver rejecting it would reject every lossy link.
Assert the mask, not only the summary bit. The one non-equivalent survivor here was exposed_mask, which named which traffic classes were in the clear while nothing read it.
Establish whether a survivor is redundant or dead. Two survivors here were equivalent and benign; batch 018's earlier one was equivalent and pointed at an unreachable branch. The classification matters and the fix differs.
20. Synthesis and Implementation Reality
No cryptography is modelled, and the real cost is where the model stops. A MAC over every flit at line rate is a throughput requirement on the crypto engine, and a design whose engine cannot keep up either stalls the link or does not protect every flit. Which of those it does is a decision that must be explicit.
Key provisioning is outside this chapter and is the hard part. Section 9 says when to rekey and nothing about how the new key is agreed — which requires the authenticated channel of 19.1 and is the reason the two chapters compose in that order.
The overhead of section 11 competes directly with 18.2's ceilings. A 16-byte MAC on a 64-byte payload is a second multiplier on the same raw rate as flit efficiency, and the two compound: a link at 75% flit efficiency and 72% security efficiency delivers 54% of its line rate as payload.
Section 12's selective protection is often not a choice. Addresses may need to be visible to the switch for routing — 16.2 established that a switch must read a destination to forward — so protecting them end-to-end conflicts with forwarding them hop-by-hop. That tension is real and this model does not resolve it; it only makes the exposure visible.
Section 14's alarm threshold needs tuning against the link's error rate. Set too low it fires on ordinary bit errors; too high and an attacker gets a large forgery budget before anybody looks. It is 18.5 section 9's noise-band problem with a security consequence attached.
21. Silicon Observability
| Observable | Why it matters |
|---|---|
| MAC verification failures, counted not just dropped | section 14 — the difference between noise and an attack |
| Replay drops, separately from verification drops | section 7 — they indicate different things |
| Bytes and sequence numbers used under the current key | section 9 — both limits, and either forces a rekey |
| Rekey events and their trigger | section 9 — a rekey nobody expected is a signal |
| Negotiated security level, per link | section 13 — the configured level is not the achieved one |
| Which traffic classes are protected | section 12 — the exposure is a policy decision, and policies drift |
The fifth row is the one that decays. A link configured for a security level and negotiated down to a lower one reports the configuration in every inventory and the negotiation nowhere — which is exactly the shape of 17.4 section 9's generation-negotiation problem, in a domain where the consequence is larger.
22. Debug Lab
Symptom: a link is configured as protected and something on it is not behaving as though it were.
Check what the negotiation actually settled on. Section 13: the configured level is a request. If the peer offered less and there is no floor, the link is running at the peer's level and the inventory says otherwise.
Check whether a MAC is present at all, or only encryption. Section 5: an encrypted link with no MAC accepts modified ciphertext and looks identical in every configuration dump.
Check the replay counter. Section 7: a receiver with no sequence check accepts every recorded flit, and every one of them verifies.
Check the sequence space against the rekey interval. Section 9: a link nowhere near its data budget can exhaust a sixteen-bit counter, and a wrapped counter silently disables section 7.
Check MAC verification failure counts. Section 14: if they are not counted, an attacker gets unlimited attempts against section 6's work factor with nothing to see.
Check which traffic classes are in the clear. Section 12: protecting payloads and leaving addresses exposed leaks the access pattern, which for a memory device is most of the information.
Recompute the effective bandwidth. Section 11: 88 bytes on the wire for 64 useful is 27% gone before 18.2's ceilings apply.
23. Design Review
Is there a MAC, or only encryption? They are separate mechanisms and only one detects modification.
How long is the MAC, and what forgery budget does that imply?
Is there a replay counter, and what happens when it wraps?
What are the two rekey limits, and which one binds first on this link?
Is the IV unique within a key epoch, and what generates it?
What does the negotiation floor allow, and can a peer take the link below it?
Which traffic classes are protected, and was leaving the others exposed a decision or a default?
What happens on a verification failure, and does anybody count?
24. How This Appears In Real Engineering
Link-security failures present as links that are configured as protected and are not, with every configuration dump agreeing that they are.
The characteristic case is encryption without integrity. Confidentiality is the property everybody names, the configuration says "encrypted", and modified ciphertext is decrypted and acted on with no indication anything happened.
The second is a negotiated downgrade. The host is configured for the highest level, the peer offers less, there is no floor, and the inventory reports the configured level forever.
The third is a wrapped sequence counter. The link is well within its data budget, the sixteen-bit counter exhausts, and the replay protection built carefully in section 7 stops working without any error.
The fourth is silent drops. Verification failures are dropped correctly and counted nowhere, so an attacker probing the MAC is indistinguishable from a slightly noisy cable.
25. Common Misconceptions
"The link is encrypted, so it is secure." Encryption is confidentiality. Modification, replay, and who is at the other end are three further questions and encryption answers none of them.
"Nobody can read it, so nobody can change it." Ciphertext an attacker cannot read is ciphertext an attacker can still flip bits in. Whether the result is useful depends on the mode; whether it is detected depends on the MAC.
"The MAC verified, so this is a fresh flit." A replayed flit has a genuine MAC over genuine data. Integrity checking cannot detect a replay, which is why the counter is separate.
"We use a strong cipher." With what MAC length? Section 6's 32-bit MAC is 2^31 expected forgeries whatever the cipher is.
"A gap in the sequence numbers means an attack." It means lost flits. Rejecting gaps rejects every lossy link; the check is against the highest seen.
"We rekey on a timer." On bytes and on sequence numbers. A link can exhaust a sixteen-bit counter well inside any time-based interval, and the wrap re-enables every old flit.
"The IV is random so it will not repeat." Over a large enough space, probably. The model's question is what happens when it does, and the answer is that a rekey resets the space — which is why sections 9 and 10 are the same mechanism seen twice.
"We protect the data." And the addresses? For a memory device the access pattern is most of what an observer wants, and it is the class most often left in the clear.
26. Interview Reasoning
Q1. A link is encrypted. What have you established? That an observer cannot read the traffic. Not that it has not been modified, not that it is not a replay, and nothing about who is at the other end.
Q2. Is encryption without a MAC a real configuration? Yes, and it is the dangerous one. An attacker cannot read the traffic and can still change it, and the receiver decrypts whatever arrives.
Q3. Is a MAC without encryption useful? Yes. The traffic is readable and cannot be modified undetected. It is a legitimate trade when confidentiality is not the requirement.
Q4. How long should a MAC be? Long enough that 2^(n−1) forgery attempts is out of reach. 64 bits is 2^63; 32 bits is 2^31, which is a budget rather than an impossibility.
Q5. Can a MAC detect a replay? No. A replayed flit carries a genuine MAC over genuine data and verifies perfectly. Replay needs a counter, and it is a separate mechanism.
Q6. Sequence jumps from 2 to 50. Attack or not? Not — 47 flits were lost. The replay check is against the highest seen, not against the next expected, because rejecting gaps rejects every lossy link.
Q7. Why rekey? Two limits. The data a key protects, and the sequence space that guarantees uniqueness. Either one exhausting forces it.
Q8. What happens when a sequence counter wraps? Every old flit looks new, and the replay protection stops working with no error anywhere. That is why the rekey is bounded by sequence space and not only by bytes.
Q9. Is an IV repeated after a rekey a problem? No. A rekey resets the IV space. Uniqueness is required within a key epoch, and a model forbidding repetition across epochs would forbid the only thing that makes a finite IV space workable.
Q10. What does security cost in bandwidth? At a 64-byte payload with a 16-byte MAC and an 8-byte IV, 27 percent — a 400Gbps link delivers 288 of payload. At an 8-byte payload it is 75 percent.
Q11. You protect payloads and leave addresses in the clear. What leaks? The access pattern — which pages, in what order, how often. For a memory device that is most of what an observer wants, and it is a strong description of what the workload is doing.
Q12. Why must negotiation have a floor? Because without one the link runs at whatever the peer claims to support, and an attacker who can influence that claim chooses the security level.
Q13. Is every downgrade a failure? No. A device that cannot do confidentiality agreeing on integrity is a legitimate outcome if integrity is above the floor. The failure is agreeing below what policy allows.
Q14. What do you do with a flit whose MAC fails? Drop it, and count it. One failure is a bit error; a run of them is an attack signature, and a receiver that drops without counting cannot tell them apart.
Q15. Where should the alarm threshold sit? Above the link's ordinary error rate and below a useful forgery budget. Too low it fires on noise; too high an attacker gets a large number of attempts unobserved.
Q16. Which of the five properties in section 15 is confidentiality? None of them. Encryption is what the link does with the payload once all five hold — it is not a condition and cannot substitute for any of them, which is why the encryption-only build calls six links of six secure.
27. Exercises
1. Extend RTL 1 with a third property — freshness as a first-class flag rather than a consequence of the counter. Does both become all three, and which of section 15's gates does it correspond to?
2. In RTL 2, model a MAC whose verification is attempted at line rate. At what forgery rate does the attempt budget in attempts_k become reachable before the link is rekeyed?
3. RTL 3 uses a single highest-seen value. Model a sliding window that tolerates reordering. What is the largest reordering it can accept without accepting a replay?
4. Using RTL 4 and RTL 3 together, find the byte budget that guarantees a rekey before a sixteen-bit sequence counter can wrap, for a given flit size.
5. In RTL 5, make the IV a counter rather than an input. Which of the uniqueness assertions become structural rather than checked?
6. RTL 6 charges a fixed MAC and IV per flit. Model a scheme that MACs a group of flits together and derive the group size at which the overhead falls below 5 percent.
7. In RTL 7, make addresses protected end-to-end and determine what 16.2's switch would need in order to still route. Is there a construction that satisfies both?
8. Add a sixth property to RTL 10 for 19.1 section 14's re-authentication freshness. Is it independent of key_fresh, and which one expires first?
28. Summary
Authentication established who is on the link; this chapter protects what crosses it.
Integrity and confidentiality are different properties. A cipher without a MAC is a link an attacker cannot read and can still modify, and the receiver acts on whatever arrives.
A MAC is a probability. 64 bits is 2^63 expected forgeries; 32 bits is 2^31, which is a budget somebody can afford.
Replay needs its own counter. Two of five flits were replays, and all five verified — integrity checking cannot detect repetition.
A key has two limits, data and sequence space, and either one alone forces a rekey. A wrapped counter silently re-enables every old flit.
An IV must be unique within a key epoch — and only within it, because the rekey is what makes a finite space workable.
Security costs bandwidth. 88 bytes on the wire for 64 useful: a 400 Gbps link delivering 288, and 75% overhead at an 8-byte payload.
What is left in the clear is a decision. Three of four traffic classes exposed by a data-only policy, including the address stream that describes the workload.
Negotiation must have a floor, or the peer chooses the security level.
And a failed verification must be counted, because one is noise and a hundred is an attack.
Encryption is not one of the five properties a secure link needs. The encryption-only build called six links of six secure, and its criterion — the traffic is unreadable — measures none of them.
19.3 — Isolation takes a link that is authenticated and protected and asks the question a shared pool raises: what stops one host reaching another's memory on a device they both legitimately use.
Continue learning
Related tutorials
- Related topic
CXL Transport on UCIe
Why carrying CXL over UCIe is not the PCIe mapping renamed — CXL brings its own multiplexer, link layer and retry, so two arbitration layers and two candidate reliability owners meet at one boundary. Flit-format lifetime, exactly-once semantic delivery under replay, protocol-class arbitration and starvation, recovery lifetimes, and two scoreboards.
- Related topic
Device Authentication
Every chapter so far assumed the device is what it claims. This chapter builds measurement against a golden value, nonce freshness, certificate chains, revocation, trust models, authentication ordering, failure policy, cost amortisation and re-authentication.
- Related topic
Isolation
Two hosts on one pooled device. This chapter builds region overlap, device-side enforcement, fault containment, residue after release, reset blast radius, shared-structure observability, the fabric-manager trust domain, capacity quotas, capability scope and the assembled isolation model.
- Related topic
Why CXL Matters
Why PCIe transaction semantics are insufficient for coherent memory and accelerator attach — CXL.io, CXL.cache and CXL.mem as the CXL Consortium defines them, device types, why coherence needs distributed per-line state, memory-window routing, what coherence costs, and why a clean transport proves nothing about coherence.
Standards & specifications
- Governing standard
- CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)
Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the CXL curriculum.
