Skip to content

Standardise log levels (#223) - #263

Open
AlfioEmanueleFresta wants to merge 12 commits into
masterfrom
chore/log-levels
Open

AlfioEmanueleFresta wants to merge 12 commits into
masterfrom
chore/log-levels

Conversation

@AlfioEmanueleFresta

Copy link
Copy Markdown
Member

Resolves #223.

Adds docs/logging.md, a logging policy for the library:

  • Level semantics: error for library faults only, warn for unexpected device or peer behaviour, info for sparse lifecycle events, debug for protocol flow and lengths, trace for raw bytes.
  • Sensitive data (PINs, secrets, tokens, key material, credential IDs, user handles) is never logged above debug, and only as a length there.
  • Messages are static string literals with structured fields, never interpolation.

It then brings the whole library in line with the policy. Most messages logged at error were really device, transport or decoding failures and now log at warn, while genuine library invariants stay at error. Expected polled conditions such as U2F user presence drop to debug. A few places were logging secrets or full credential lists above debug, including key material and the preflight list. Those now log a length or move to trace. All interpolated messages and one stray println become structured tracing calls.

A custom dylint lint in lints/ enforces the mechanical rules (static messages, no print family, no log crate, and a best-effort sensitive-name heuristic) and runs in CI.

Defines tracing level semantics (error for library faults only, warn for
unexpected device behaviour, info for sparse lifecycle, debug for protocol
flow and lengths, trace for raw bytes), the sensitive-data rule, and the
static-message-with-structured-fields form. Closes the policy half of #223.
A logging_lints dylint library enforcing the mechanical parts of the
logging policy:

- tracing_message_interpolation: messages must be static string literals
- print_macro_in_library: no print/println/eprint/eprintln
- log_crate_macro: no log crate macros
- sensitive_field_above_debug: best-effort denylist at info and above

Registered in the workspace metadata and run in CI, where the three
deterministic lints fail the build. The heuristic stays a warning.
Downgrade peer, transport, IO and decode failures from error! to warn!,
keeping error! only for in-memory crypto invariants. Reduce byte-array
dumps to lengths at debug! with full values at trace!, and stop the
trial-decrypt span from recording the EID key.
Wire-framing and packet-validation failures are device behaviour, so
they move from error! to warn!. Demote the device-blink notices to
debug! and use static messages with structured fields.
Downgrade connect, GATT, read/write and pairing failures from error! to
warn!, demote the device-cancelled notice to debug!, and reduce the
service-data dump to a length at debug!.
NFC/PC-SC transport faults move from error! to warn! and the stray
println! becomes a warn!. U2F APDU non-success status words are an
expected, polled part of the flow, so they log at debug! rather than
error!. Raw APDU buffers stay at trace! with static messages.
Malformed or unknown device responses move from error! to warn!. The
preflight credential dumps were logging credential IDs at info!, which
is sensitive: keep only a count at debug! and move the full lists to
trace!. Replace interpolated messages with structured fields.
Stop logging AES key material and ciphertext: log a length instead.
Peer public-key and decrypt failures move from error! to warn!, while
genuine crypto invariants (HMAC/HKDF/cipher build on fixed inputs) stay
at error!. Replace interpolated messages with structured fields.
Malformed Client PIN responses and device faults move from error! to
warn!, keeping error! for in-memory invariants. Reduce the pinUvAuthToken
and HMAC-secret outputs to lengths, and replace interpolated messages
with structured fields.
Replace interpolated messages with structured fields so the virt test
harness satisfies the logging lints.

@msirringhaus msirringhaus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this! Some minor questions inline, but overall very nice!

Comment thread docs/logging.md
trace!(?apdu, "Received APDU");
```

Prefer a span over repeating the same field on every event. A ceremony or a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add an example here?

Comment thread docs/logging.md
- large-blob plaintext
- private keys
- credential IDs
- user handles and user names

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we feel about hardware specific things (e.g. USB device address)?

Some(modality) => Ok(modality),
None => {
warn!("Channel did not return modality.");
warn!("Channel did not return modality");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the absence of a full stop at the end of the line enforced? Should this be mentioned in logging.md in the "how to write"-section?

} else {
error!("Failed to split HMAC Secret outputs. Unexpected output length: {}. Skipping HMAC extension", output.len());
warn!(
output_len = output.len(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this violating the rules?

These are sensitive and must never be logged above debug!:

  • HMAC salts and outputs, and PRF values
    ...
    At debug! log only a length or a presence flag.

Should such a situation be dealt with by having two loggings at the same place, with different levels of information?

warn!(
total = out.len(),
"largeBlobArray exceeded {LARGE_BLOB_MAX_ARRAY_BYTES}, aborting"
{ total = out.len(), cap = LARGE_BLOB_MAX_ARRAY_BYTES },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, as mentioned above. Logging the lenght in warn()?

}

#[instrument]
#[instrument(level = Level::DEBUG, skip_all)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some sentence or two about these in logging.md as well?

frame_len = decrypted_frame.len(),
padding_len, "Padding length exceeds frame length"
warn!(
{ frame_len = decrypted_frame.len(), padding_len },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fine to log at warn? Not entirely sure right now, what the decrypted frame contains.

[target.'cfg(all())']
rustflags = ["-C", "linker=dylint-link"]

# For Rust versions 1.74.0 and onward, the following alternative can be used

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't you pinning the nightly rust further down for this crate? Should this then be already available?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardise log levels

2 participants