Re: [PATCH v15 7/9] tls: device: add RX KeyUpdate support

Rishikesh Jethwani <[email protected]>
Newsgroups org.kernel.vger.netdev
Message-ID <CAKaoeS0czD+ksg=R=Vu+HH0nJfa0+q1i03u9cw0stiAiUjOVsg@mail.gmail.com>
On Thu, Jul 23, 2026 at 2:16 AM Paolo Abeni <[email protected]> wrote:
>
> From: AI Reviewer <[email protected]>
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/
> ---
> tls: device: add RX KeyUpdate support
>
> Adds RX KeyUpdate support for TLS device offload, retaining the old
> AEAD/IV/rec_seq on tls_offload_context_rx so in-flight records that the
> NIC already decrypted with the old key can still be processed, and
> deferring installation of the new key until copied_seq crosses
> old_nic_boundary.
>
> > diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.c
> > index 337201f12895..71c9d3ca947d 100644
> > --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.c
> > +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.c
> > @@ -86,10 +86,20 @@ static int mlx5e_ktls_resync(struct net_device *netdev,
> >       return 0;
> >  }
> >
> > +static void mlx5e_ktls_rx_rekey_fixup(struct sk_buff *skb)
> > +{
> > +     struct sk_buff *frag_iter;
> > +
> > +     skb->decrypted = 1;
> > +     skb_walk_frags(skb, frag_iter)
> > +             frag_iter->decrypted = 1;
> > +}
> > +
> >  static const struct tlsdev_ops mlx5e_ktls_ops = {
> >       .tls_dev_add = mlx5e_ktls_add,
> >       .tls_dev_del = mlx5e_ktls_del,
> >       .tls_dev_resync = mlx5e_ktls_resync,
> > +     .tls_dev_rx_rekey_fixup = mlx5e_ktls_rx_rekey_fixup,
> >  };
>
> [Medium]
> Is this RX rekey infrastructure safe to enable unconditionally for every
> HW-offloaded TLS RX socket?  Only mlx5 implements tls_dev_rx_rekey_fixup
> here.  Other in-tree drivers that advertise NETIF_F_HW_TLS_RX (for
> example nfp) will still enter the rekey state machine with a NULL
> fixup pointer, so the whole-record-mangled-by-NIC scenario the fixup
> was introduced to handle would be uncovered on those NICs.
>
> Would gating RX rekey on explicit driver opt-in (a capability flag or
> the presence of tls_dev_rx_rekey_fixup) be more appropriate than
> assuming all NICs behave like ConnectX-6 Dx?

RX KeyUpdate is TLS 1.3-only, the state machine is armed exclusively
by a TLS 1.3 post-handshake KeyUpdate; in TLS 1.2 old_aead_recv
is never set and the fixup site is never reached. Among in-tree
drivers advertising NETIF_F_HW_TLS_RX, only mlx5 supports TLS 1.3
offload, nfp rejects anything but TLS 1.2,
so it cannot enter this path. The fixup is genuinely optional: it
re-asserts skb->decrypted for an mlx5 mixed-record auth-failure quirk.
I've
separately verified on Broadcom Thor2 (TLS 1.3 RX) that no fixup is
needed there, confirming it's device-specific rather than a generic
capability gap.

> [ ... ]
>
> > diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
> > index ef837bbd9560..74583433b593 100644
> > --- a/net/tls/tls_device.c
> > +++ b/net/tls/tls_device.c
> > @@ -66,8 +66,14 @@ static void tls_device_free_ctx(struct tls_context *ctx)
> >               kfree(offload_ctx);
> >       }
> >
> > -     if (ctx->rx_conf == TLS_HW)
> > -             kfree(tls_offload_ctx_rx(ctx));
> > +     if (ctx->rx_conf == TLS_HW) {
> > +             struct tls_offload_context_rx *offload_ctx =
> > +                     tls_offload_ctx_rx(ctx);
> > +
> > +             memzero_explicit(&offload_ctx->rekey,
> > +                              sizeof(offload_ctx->rekey));
> > +             kfree(offload_ctx);
> > +     }
>
> [Medium]
> Can this silently overwrite offload_ctx->rekey.old_aead_recv without
> first freeing it?
>
> memzero_explicit() clears the field but does not call crypto_free_aead(),
> so if any path reaches tls_device_free_ctx() with old_aead_recv still
> non-NULL, the crypto_aead transform and the AEAD key material it holds
> are leaked, and the explicit-zero intent is defeated for that material.
>
> Current callers appear to free old_aead_recv earlier in
> tls_device_offload_cleanup_rx(), so no leak fires today.  Would it be
> safer to call crypto_free_aead(offload_ctx->rekey.old_aead_recv)
> before the memzero_explicit(), to make this robust against future
> paths (for example races between tls_device_down and sk_destruct)?

No leak today but I'll add a defensive crypto_free_aead() before the memzero.

> [ ... ]
>
> > +static int tls_device_dev_add_rx(struct sock *sk, struct tls_context *tls_ctx,
> > +                              struct net_device *netdev,
> > +                              struct tls_crypto_info *crypto_info,
> > +                              u32 cur_seq, bool is_rekey)
> > +{
> [ ... ]
> > +     if (!rc) {
> > +             clear_bit(TLS_RX_DEV_DEGRADED, &tls_ctx->flags);
> > +             clear_bit(TLS_RX_DEV_CLOSED, &tls_ctx->flags);
> > +             if (is_rekey)
> > +                     TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
> > +     } else if (is_rekey) {
> > +             set_bit(TLS_RX_DEV_DEGRADED, &tls_ctx->flags);
> > +             set_bit(TLS_RX_DEV_CLOSED, &tls_ctx->flags);
> > +             TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYFALLBACK);
> > +     }
> > +     return rc;
> > +}
>
> [Medium]
> Is TLS_RX_DEV_CLOSED being repurposed in a way that could confuse
> existing consumers?
>
> Previously the bit denoted permanent teardown, used by tls_device_down()
> and tls_device_offload_cleanup_rx() to avoid a double tls_dev_del.  This
> patch also sets/clears it as a transient state during rekey (set before
> tls_dev_del in the rekey path, cleared inside tls_device_dev_add_rx() on
> success, and toggled by tls_device_rx_del_key() when a KeyUpdate record
> is parsed), while other checks (tls_device_rx_resync_new_rec early
> return, tls_device_offload_cleanup_rx skip-dev_del logic) still treat it
> as terminal.
>
> Would splitting the transient rekey state into a separate bit make the
> semantics less error-prone for future changes?

I don't think a separate bit is needed, the premise that
TLS_RX_DEV_CLOSED meant "permanent teardown" isn't quite right. It has
always meant the
narrower, non-terminal invariant:

  tls_dev_del was called for RX → the NIC currently holds no RX offload context.

"Permanent" was just emergent, since teardown used to be the only
caller of tls_dev_del. The rekey path uses the bit with that same
meaning: it sets the
bit because it actually calls tls_dev_del, and clears it because
tls_dev_add actually re-establishes the NIC context.

The readers you flagged stay correct under this, because they key off
"NIC context absent," not "socket dead":
  - resync early return: you must not resync a NIC with no context, so
suppressing it during the rekey window is exactly right.
  - cleanup_rx skip-dev_del: correct double-del guard if the socket
closes mid-rekey.

A separate transient bit would have identical set/clear points and add
a new "keep the two in sync" hazard.

Your point about the name/comment reading as terminal is fair, so I'll
tighten the comment to state the invariant explicitly.

> [ ... ]
>
> > +void tls_device_rx_del_key(struct sock *sk, struct tls_context *ctx)
> > +{
> [ ... ]
> > +     set_bit(TLS_RX_DEV_CLOSED, &ctx->flags);
> > +     synchronize_net();
> > +     netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
> > +                                     TLS_OFFLOAD_CTX_DIR_RX);
> > +     up_read(&device_offload_lock);
> > +}
>
> [ ... ]
>
> > +static int tls_device_reencrypt_old_key(struct sock *sk,
> > +                                     struct tls_offload_context_rx *ctx,
> > +                                     struct tls_sw_context_rx *sw_ctx,
> > +                                     struct tls_context *tls_ctx)
> > +{
> > +     struct crypto_aead *saved_aead = sw_ctx->aead_recv;
> > +     char saved_iv[TLS_MAX_IV_SIZE + TLS_MAX_SALT_SIZE];
> > +     char saved_rec_seq[TLS_MAX_REC_SEQ_SIZE];
> > +     int ret;
> [ ... ]
> > +     sw_ctx->aead_recv = saved_aead;
> > +     memcpy(tls_ctx->rx.iv, saved_iv, sizeof(saved_iv));
> > +     memcpy(tls_ctx->rx.rec_seq, saved_rec_seq, sizeof(saved_rec_seq));
> > +
> > +     if (ret)
> > +             return ret;
> [ ... ]
> > +     return 0;
> > +}
>
> [Low]
> Should saved_iv[] and saved_rec_seq[] be scrubbed with
> memzero_explicit() before this function returns?
>
> They hold TLS 1.3 traffic-secret-derived IV material copied from
> tls_ctx->rx.iv and rx.rec_seq.  Elsewhere in this subsystem
> memzero_explicit() is used consistently to scrub such material
> (tls_ctx_free, tls_sw_ctx_finalize, and the new memzero on
> offload_ctx->rekey in tls_device_free_ctx).  Leaving the buffers
> un-scrubbed on the stack seems inconsistent with the surrounding
> hygiene.

  I'd rather leave this as-is. Neither buffer holds sensitive material:
  saved_rec_seq is the record sequence counter, and saved_iv is the
  static IV/salt, whose secrecy AEAD security doesn't depend on (the
  nonce is effectively public), the actual secret is the key, which
  never touches these buffers.

  The identical bytes also live in tls_ctx->rx.iv / rx.rec_seq for the
  whole connection and aren't scrubbed there either; tls_ctx_free() only
  zeroes crypto_send/crypto_recv, not the tx/rx working copies. So
  scrubbing the transient stack copy wouldn't close a real gap, just add
  inconsistency with how the live copies are handled.

> > +
> >  int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx)
> >  {
> >       struct tls_offload_context_rx *ctx = tls_offload_ctx_rx(tls_ctx);
> >       struct tls_sw_context_rx *sw_ctx = tls_sw_ctx_rx(tls_ctx);
> >       struct sk_buff *skb = tls_strp_msg(sw_ctx);
> > +     u32 copied_seq = tcp_sk(sk)->copied_seq;
> >       struct strp_msg *rxm = strp_msg(skb);
> >       int is_decrypted, is_encrypted;
> > +     u32 rec_start_seq;
> >
> >       if (!tls_strp_msg_mixed_decrypted(sw_ctx)) {
> >               is_decrypted = skb->decrypted;
> > @@ -1098,10 +1225,42 @@ int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx)
> >               is_encrypted = 0;
> >       }
> >
> > -     trace_tls_device_decrypted(sk, tcp_sk(sk)->copied_seq - rxm->full_len,
> > +     rec_start_seq = sw_ctx->strp.copy_mode
> > +             ? copied_seq - rxm->full_len
> > +             : copied_seq;
> > +
> > +     trace_tls_device_decrypted(sk, rec_start_seq,
> >                                  tls_ctx->rx.rec_seq, rxm->full_len,
> >                                  is_encrypted, is_decrypted);
>
> [Medium]
> Is this an intentional silent change to the first argument of the
> trace_tls_device_decrypted() tracepoint?
>
> The previous expression was unconditionally copied_seq - rxm->full_len;
> the new form is copied_seq for the non-copy_mode case.  Observability
> tools consuming this tracepoint may see a different value for the same
> record boundary.
>
> The same rec_start_seq is also fed into the classification below via
> before(rec_start_seq, ctx->rekey.old_nic_boundary).  If either branch of
> the ternary is off by one full record, the record would be routed to
> the wrong rekey path (drop-and-increment-old-seq vs reencrypt-with-old
> -key).  Could the commit message describe why the two modes use
> different formulas, and how the choice was validated?

The key fact is that tcp_sk(sk)->copied_seq is advanced at different times in
the two strparser modes, so the same subtraction cannot describe the record
start in both:

  - copy_mode: the record's bytes are pulled out of the TCP receive queue up
  front, before tls_device_decrypted() runs. tls_strp_msg_cow() calls
  tcp_read_done(sk, stm.full_len) (tls_strp.c:135), which bumps copied_seq by
  full_len. So at decrypt time copied_seq already sits at the end of the
  current record, and copied_seq - full_len is the record start.
  - non-copy mode: tcp_read_done() is deferred to tls_strp_msg_consume()
  (tls_strp.c:599-600), which runs after the record is processed. The anchor is
  loaded starting at copied_seq (tls_strp_load_anchor_with_queue(),
  tls_strp.c:466 — the offset there is the byte position of copied_seq within
  its skb, not a seq delta to the record). So at decrypt time copied_seq still
  points at the start of the record, and no subtraction is wanted.

  So the ternary is not off by a record, both branches compute the same
  logical quantity (the sequence number of the first byte of the record),
  compensating for copied_seq's differing meaning. The before(rec_start_seq,
  old_nic_boundary) classification is therefore fed a consistent value in both
  modes.

  On the observability concern: the value did change for the non-copy path, and
  that's actually a fix. The old unconditional copied_seq - full_len was
  correct only in copy_mode; on the non-copy fast path (the common case) it
  pointed one full record too early. This series makes that argument
  load-bearing for rekey routing, so it had to be corrected.

  I'll add a note to the commit message spelling this out.

> >
> > +     if (unlikely(ctx->rekey.old_aead_recv)) {
> > +             bool before_nic_boundary =
> > +                     before(rec_start_seq, ctx->rekey.old_nic_boundary);
> > +
> > +             if (before_nic_boundary) {
> > +                     if (is_encrypted) {
> > +                             tls_bigint_increment(ctx->rekey.old_rec_seq,
> > +                                                  tls_ctx->prot_info.rec_seq_size);
> > +                             return 0;
> > +                     }
> > +
> > +                     /* rekey_fixup sets decrypted flags in case if NIC clears
> > +                      * decrypted flags on auth failure
> > +                      */
> > +                     if (!is_decrypted && ctx->rekey.rekey_fixup)
> > +                             ctx->rekey.rekey_fixup(skb);
> > +
> > +                     return tls_device_reencrypt_old_key(sk, ctx,
> > +                                                         sw_ctx, tls_ctx);
> > +             }
>
> [High]
> Does the classification here miss the whole-record NIC-auth-failure
> case?
>
> The comment and the mlx5e_ktls_rx_rekey_fixup callback state that the
> NIC may clear skb->decrypted on auth failure.  If the NIC has already
> XORed the payload with the old key's keystream and then cleared
> skb->decrypted on every fragment (not mixed), is_encrypted computes as
> 1 in the !tls_strp_msg_mixed_decrypted branch:
>
>     if (!tls_strp_msg_mixed_decrypted(sw_ctx)) {
>         is_decrypted = skb->decrypted;   /* 0 */
>         is_encrypted = !is_decrypted;    /* 1 */
>     }
>
> The code then advances old_rec_seq and returns 0, feeding the record to
> SW which will attempt decryption with the new key and fail AEAD
> verification.  Only the mixed sub-case reaches ctx->rekey.rekey_fixup.
> Should the whole-record path also invoke rekey_fixup and
> reencrypt-with-old-key?

You're right that a non-mixed record with skb->decrypted clear lands
in the is_encrypted branch and gets handed straight to
SW. The reason that's safe comes down to what the NIC actually does on
auth-failure clearing.

The premise in the fixup comment ("NIC clears decrypted flags on auth
failure") only ever manifests as a mixed record on this hardware, the
NIC never
clears decrypted uniformly across all fragments of a record after
having XORed the payload with the old key. I confirmed this on
ConnectX-6 Dx across
repeated KeyUpdate cycles: auth-failure clearing always produces a
mixed record, which is classified as neither is_encrypted nor
is_decrypted and
therefore falls through to the rekey_fixup + reencrypt_old_key path,
exactly the path you're pointing at.

So a non-mixed decrypted==0 record is genuinely untouched wire
ciphertext: the NIC never touched the payload, and SW can decrypt it
directly after
advancing old_rec_seq. The whole-record XOR-then-clear case you
describe doesn't occur, so it would never reach the is_encrypted
fast-path.

That said, the invariant was implicit and load-bearing, which is what
made your question necessary in the first place. I'll add a comment at
the branch
spelling it out.

> [Medium]
> Can old_rec_seq drift after tls_device_rx_del_key() has already torn
> down the NIC key?
>
> Sequence:
>
>   1. KeyUpdate is parsed by SW; tls_check_pending_rekey() ->
>      tls_device_rx_del_key() calls tls_dev_del, so the NIC no longer
>      holds any key.
>
>   2. Records that arrive next are wire-encrypted with the peer's new
>      key and reach tls_device_decrypted() with is_encrypted == 1 while
>      rec_start_seq is still before old_nic_boundary.
>
>   3. This code increments ctx->rekey.old_rec_seq for each such record.
>
> If any later record in the same window turns out to be mixed
> (!is_encrypted && !is_decrypted), tls_device_reencrypt_old_key() will
> use the inflated old_rec_seq as the AES-GCM nonce input.  Would that
> produce ciphertext that does not match what the NIC actually XORed with
> the old key?

No, old_rec_seq isn't inflated, and the mixed record can't appear
after the encrypted ones in that window.

old_rec_seq tracks the TLS record sequence number, which advances by
one for every record regardless of key. Both before-boundary paths
step it by exactly
one per record: the is_encrypted branch increments and returns 0, and
tls_device_reencrypt_old_key() increments after re-running the AEAD.
So the
increment on encrypted records is required to keep old_rec_seq aligned
with the record a later reencrypt would touch, omitting it is what
would desync
the nonce.

The ordering in the scenario also can't happen.
tls_device_rx_del_key() removes the NIC key at KeyUpdate, and it's
only re-added by the deferred
tls_device_deferred_dev_add_rx() once rec_start_seq crosses
old_nic_boundary. For the whole before-boundary window the NIC holds
no key, so records there
are untouched wire ciphertext (is_encrypted == 1). A record can only
be mixed (!is_encrypted && !is_decrypted) if the NIC XORed part of it
with the old
key, which requires the old key to still be installed. So in sequence
order you get old-key (decrypted/mixed) records, then the KeyUpdate,
then new-key
encrypted records; you can't get a new-key encrypted record followed
by an old-key mixed record before the boundary.

The genuinely mixed case, a record straddling old_nic_boundary, feeds
the reencrypt path with the correct old_rec_seq, since every prior
before-boundary
record stepped it by one.

> > +
> > +             crypto_free_aead(ctx->rekey.old_aead_recv);
> > +             ctx->rekey.old_aead_recv = NULL;
> > +
> > +             if (ctx->dev_add_pending)
> > +                     tls_device_deferred_dev_add_rx(sk, tls_ctx, ctx);
> > +     }
> > +
>
> [ ... ]
>
> > @@ -1612,29 +1789,88 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
> [ ... ]
> > +     rc = tls_sw_ctx_init(sk, 0, new_crypto_info);
> >       if (rc)
> >               goto release_ctx;
> >
> > -     rc = netdev->tlsdev_ops->tls_dev_add(netdev, sk, TLS_OFFLOAD_CTX_DIR_RX,
> > -                                          &ctx->crypto_recv.info,
> > -                                          tcp_sk(sk)->copied_seq);
> > -     info = (void *)&ctx->crypto_recv.info;
> > -     trace_tls_device_offload_set(sk, TLS_OFFLOAD_CTX_DIR_RX,
> > -                                  tcp_sk(sk)->copied_seq, info->rec_seq, rc);
> > -     if (rc)
> > -             goto free_sw_resources;
> > +     if (!context->dev_add_pending) {
> > +             rc = tls_device_dev_add_rx(sk, ctx, netdev, src_crypto_info,
> > +                                        copied_seq, !!new_crypto_info);
> > +             if (!new_crypto_info) {
> > +                     if (rc)
> > +                             goto free_sw_resources;
> > +                     tls_device_attach(ctx, sk, netdev);
> > +             }
> > +     } else if (!was_dev_add_pending) {
> > +             TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYINPROGRESS);
> > +     } else {
> > +             TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
> > +     }
> > +
> > +     tls_sw_ctx_finalize(sk, 0, new_crypto_info);
>
> [High]
> Does this path silently swallow a real HW rekey failure?
>
> When new_crypto_info != NULL and dev_add_pending is 0,
> tls_device_dev_add_rx() is called and rc is stored, but the immediate
> error handling is guarded by if (!new_crypto_info).  On the rekey path
> control falls through to tls_sw_ctx_finalize() and eventually the
> return 0 at the end of the success block.
>
> Meanwhile tls_device_dev_add_rx() has already bumped
> LINUX_MIB_TLSRXREKEYFALLBACK and set TLS_RX_DEV_DEGRADED /
> TLS_RX_DEV_CLOSED, and the NIC-side context has been torn down.
>
> In do_tls_setsockopt_conf() the intended contract is:
>
>     } else if (update && ctx->rx_conf == TLS_HW) {
>         /* HW rekey failed - return the actual error.
>          * Cannot fall back to SW for an existing HW connection.
>          */
>         goto err_crypto_info;
>     }
>
> But because rc is effectively discarded, that branch is never taken —
> setsockopt() returns 0, rx_conf stays TLS_HW, and telemetry, return
> code and state are all inconsistent.  Should the rekey branch also
> propagate rc (for example goto release_ctx / err_crypto_info) when
> tls_device_dev_add_rx() fails?

The mechanical observation is correct, on the rekey path rc from
tls_device_dev_add_rx() is intentionally not propagated,
but this is by design, not a swallowed failure.

The TLS_RX_DEV_DEGRADED | TLS_RX_DEV_CLOSED bits that tls_device_dev_add_rx()
sets on failure are the existing degraded-mode mechanism. Once set, HW
resync is skipped and records are decrypted in software via
sw_ctx->aead_recv,
which on this path was just installed with the new key by
tls_sw_ctx_init()/tls_sw_ctx_finalize(). So the connection keeps
working under the new key, just
in SW. That is exactly what LINUX_MIB_TLSRXREKEYFALLBACK counts.

So {REKEYFALLBACK++, return 0, rx_conf == TLS_HW} isn't inconsistent,
it's the definition of a degraded HW-offload connection (same as
non-rekey degraded
mode): still an offload context, decrypting in SW. Returning 0 is
correct because the rekey succeeded from the socket's point of view.

The rekey path does return an error, via goto release_ctx, when
tls_sw_ctx_init() itself fails (e.g. ENOMEM). That's the only case
where the else if
(update && ctx->rx_conf == TLS_HW) branch in do_tls_setsockopt_conf()
fires for RX, and there returning the error is correct because the SW
fallback
couldn't be built.

> [Medium]
> Can TLSRXREKEYOK be double-counted, and TLSRXREKEYINPROGRESS leak?
>
> When was_dev_add_pending is true and dev_add_pending remains 1
> (a second rekey arrives while the first is still deferred), this
> branch runs:
>
>     } else {
>         TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
>     }
>
> No NIC add has occurred yet.  Later, when copied_seq crosses
> old_nic_boundary, tls_device_deferred_dev_add_rx() calls
> tls_device_dev_add_rx(..., is_rekey=true), which on success increments
> TLSRXREKEYOK again.
>
> The superseded first rekey's TLSRXREKEYINPROGRESS also does not appear
> to be decremented in this chained-rekey path.  Is the accounting
> intended to work that way?

Neither the double-count nor the leak happens, the accounting balances
across a chained rekey.

> [ ... ]
>
> > diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
> [ ... ]
> > @@ -1098,10 +1225,42 @@ int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx)
> [ ... ]
> > +                     /* rekey_fixup sets decrypted flags in case if NIC clears
> > +                      * decrypted flags on auth failure
> > +                      */
>
> [Low]
> The phrase "in case if" reads as either "in case" or "if" but not both.

Right, that's redundant wording. I'll fix it.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.