* [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers @ 2026-07-30 6:42 luoqing 2026-08-04 1:50 ` Jakub Kicinski 2026-08-04 6:50 ` [PATCH net v2] " luoqing 0 siblings, 2 replies; 5+ messages in thread From: luoqing @ 2026-07-30 6:42 UTC (permalink / raw) To: dsahern, idosch, davem, edumazet, kuba, pabeni Cc: horms, netdev, linux-kernel From: Qing Luo <luoqing@kylinos.cn> sctp_auth_calculate_hmac() silently returns when sctp_auth_asoc_create_secret() fails under memory pressure, leaving the digest zeroed. Callers then compare the zeroed digest against the saved one, getting SCTP_IERROR_BAD_SIG instead of the correct SCTP_IERROR_NOMEM. Change sctp_auth_calculate_hmac() to return int so callers can distinguish allocation failures from bad signatures: - sctp_sf_authenticate() frees save_digest and returns SCTP_IERROR_NOMEM on failure. - sctp_packet_pack() frees the auth chunk and returns 0 (drop the packet). In the non-GSO path nskb equals head, so only free nskb in the GSO path to avoid a double-free; the caller handles head cleanup when pkt_count is 0. Also update the declaration in auth.h to match. Assisted-by: LLM # review Signed-off-by: Qing Luo <luoqing@kylinos.cn> --- include/net/sctp/auth.h | 6 +++--- net/sctp/auth.c | 6 ++++-- net/sctp/output.c | 10 ++++++++-- net/sctp/sm_statefuns.c | 9 ++++++--- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h index 6f2cd562b1de..eeb3297fe97d 100644 --- a/include/net/sctp/auth.h +++ b/include/net/sctp/auth.h @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, const struct sctp_association *asoc); int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc); -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp); +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp); void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); diff --git a/net/sctp/auth.c b/net/sctp/auth.c index c901d373af80..4db3a3a4a457 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -613,7 +613,7 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) * zero (as shown in Figure 6) followed by all chunks that are placed * after the AUTH chunk in the SCTP packet. */ -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, struct sk_buff *skb, struct sctp_auth_chunk *auth, struct sctp_shared_key *ep_key, gfp_t gfp) { @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, /* ep_key can't be NULL here */ asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); if (!asoc_key) - return; + return -ENOMEM; free_key = 1; } @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, if (free_key) sctp_auth_key_put(asoc_key); + + return 0; } /* API Helpers */ diff --git a/net/sctp/output.c b/net/sctp/output.c index 23e96305cad7..a7930cd9400e 100644 --- a/net/sctp/output.c +++ b/net/sctp/output.c @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, } if (auth) { - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, - packet->auth->shkey, gfp); + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, + packet->auth->shkey, gfp)) { + sctp_chunk_free(packet->auth); + packet->auth = NULL; + if (gso) + kfree_skb(nskb); + return 0; + } /* free auth if no more chunks, or add it back */ if (list_empty(&packet->chunk_list)) sctp_chunk_free(packet->auth); diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index b01db33bbc36..b68de2c4dc26 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -4455,9 +4455,12 @@ static enum sctp_ierror sctp_sf_authenticate( memset(digest, 0, sig_len); - sctp_auth_calculate_hmac(asoc, chunk->skb, - (struct sctp_auth_chunk *)chunk->chunk_hdr, - sh_key, GFP_ATOMIC); + if (sctp_auth_calculate_hmac(asoc, chunk->skb, + (struct sctp_auth_chunk *)chunk->chunk_hdr, + sh_key, GFP_ATOMIC)) { + kfree(save_digest); + return SCTP_IERROR_NOMEM; + } /* Discard the packet if the digests do not match */ if (crypto_memneq(save_digest, digest, sig_len)) { -- 2.25.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers 2026-07-30 6:42 [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers luoqing @ 2026-08-04 1:50 ` Jakub Kicinski 2026-08-04 6:50 ` [PATCH net v2] " luoqing 1 sibling, 0 replies; 5+ messages in thread From: Jakub Kicinski @ 2026-08-04 1:50 UTC (permalink / raw) To: l1138897701 Cc: Jakub Kicinski, dsahern, idosch, davem, edumazet, pabeni, horms, netdev, linux-kernel This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible. --- --- Patch [1]: [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers --- commit 13a8cd0a9fda699784540030a5b3eada76f4de88 Author: Qing Luo <luoqing@kylinos.cn> sctp: auth: propagate HMAC calculation errors to callers This changes sctp_auth_calculate_hmac() from void to int so that an allocation failure in sctp_auth_asoc_create_secret() is reported to the caller. sctp_sf_authenticate() now returns SCTP_IERROR_NOMEM and sctp_packet_pack() drops the packet by returning 0. A few questions about the changelog and the new error paths below. The changelog says: > sctp_auth_calculate_hmac() silently returns when > sctp_auth_asoc_create_secret() fails under memory pressure, > leaving the digest zeroed. Callers then compare the zeroed > digest against the saved one, getting SCTP_IERROR_BAD_SIG > instead of the correct SCTP_IERROR_NOMEM. [Severity: High] [Finding: main-1-0] [Sources: opus-5] This isn't a bug introduced by this patch, but is a wrong error code really the whole story for the pre-patch behaviour? In sctp_sf_authenticate() the in-chunk digest is zeroed right before the HMAC call, and save_digest is a verbatim copy of the peer supplied HMAC: save_digest = kmemdup(digest, sig_len, GFP_ATOMIC); if (!save_digest) goto nomem; memset(digest, 0, sig_len); sctp_auth_calculate_hmac(...); /* returned early, digest stays 0 */ if (crypto_memneq(save_digest, digest, sig_len)) { If sctp_auth_calculate_hmac() returns without computing anything, doesn't crypto_memneq() then compare the attacker supplied bytes against zeros, so that a peer sending an all-zero HMAC field matches and the chunk is accepted with chunk->auth = 1 and SCTP_IERROR_NO_ERROR? The preconditions look reachable from the wire: the peer picks a configured but non-active shkey id, so the sctp_auth_asoc_create_secret() branch is taken, and only the GFP_ATOMIC allocation failure is not attacker controlled. The same path is reached via sctp_auth_chunk_verify() for the AUTH chunk bundled with COOKIE ECHO, so association setup is affected too. Should the changelog describe that impact instead of the error code, and should the patch carry Fixes: and Cc: stable@vger.kernel.org tags so it reaches stable trees? > diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h > index 6f2cd562b1de6..eeb3297fe97d3 100644 > --- a/include/net/sctp/auth.h > +++ b/include/net/sctp/auth.h > @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, > const struct sctp_association *asoc); > int sctp_auth_recv_cid(enum sctp_cid chunk, > const struct sctp_association *asoc); > -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > - struct sk_buff *skb, struct sctp_auth_chunk *auth, > - struct sctp_shared_key *ep_key, gfp_t gfp); > +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, > + struct sk_buff *skb, struct sctp_auth_chunk *auth, > + struct sctp_shared_key *ep_key, gfp_t gfp); > void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); > void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); > > diff --git a/net/sctp/auth.c b/net/sctp/auth.c > index c901d373af803..4db3a3a4a4574 100644 > --- a/net/sctp/auth.c > +++ b/net/sctp/auth.c > @@ -613,7 +613,7 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) > * zero (as shown in Figure 6) followed by all chunks that are placed > * after the AUTH chunk in the SCTP packet. > */ > -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, > struct sk_buff *skb, struct sctp_auth_chunk *auth, > struct sctp_shared_key *ep_key, gfp_t gfp); [Severity: Low] [Finding: main-2-3] [Sources: opus-5] This isn't a bug, but the prototype in include/net/sctp/auth.h was re-indented for the shorter int return type while the definition here keeps the old indentation, so the two argument continuation lines sit one column past the open parenthesis. checkpatch.pl --strict flags this as "Alignment should match open parenthesis". The same applies to the wrapped call in sctp_packet_pack() below, where the added if ( wrapper shifts the open parenthesis four columns right but the continuation line moves by only three. > @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > /* ep_key can't be NULL here */ > asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); > if (!asoc_key) > - return; > + return -ENOMEM; > > free_key = 1; > } > @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > > if (free_key) > sctp_auth_key_put(asoc_key); > + > + return 0; > } > > /* API Helpers */ > diff --git a/net/sctp/output.c b/net/sctp/output.c > index 23e96305cad7c..a7930cd9400e3 100644 > --- a/net/sctp/output.c > +++ b/net/sctp/output.c > @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, > } > > if (auth) { > - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, > - packet->auth->shkey, gfp); > + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, > + packet->auth->shkey, gfp)) { > + sctp_chunk_free(packet->auth); > + packet->auth = NULL; > + if (gso) > + kfree_skb(nskb); > + return 0; > + } [Severity: Low] [Finding: main-1-1] [Sources: opus-5] Is returning 0 here enough for the caller to tell what happened? In sctp_packet_pack() the value 0 is already used for the PMTU overflow bail out and for the alloc_skb() failure, and sctp_packet_transmit() treats all of them the same way: pkt_count = sctp_packet_pack(packet, head, gso, gfp); if (!pkt_count) { kfree_skb(head); goto out; } so an -ENOMEM from the HMAC path is dropped with no SNMP counter and no rate limited log, and sctp_packet_transmit() still returns 0 to sctp_outq_flush(). For an AUTH protected association using a non-active shkey this can repeat on every flush while memory is tight. This also adds the first post-merge "drop the whole packet" path for the non-GSO case, which previously always returned a non-zero pkt_count once the merge loop had run. Would it be simpler to materialize the association secret for a non-active shkey in sctp_packet_bundle_auth(), where the failure can be reported as an SCTP_XMIT status: auth = sctp_make_auth(asoc, chunk->shkey->key_id); if (!auth) return retval; leaving the packing loop infallible, or alternatively to propagate a negative errno so the caller can separate a transient ENOMEM from "nothing packable"? For the record, two related concerns turned out to be fine: DATA chunks already removed from packet->chunk_list stay on the transport's transmitted list and are recovered by RTO retransmission, and the tp->rto_pending / chunk->rtt_in_progress state set in the merge loop is cleared on retransmission in net/sctp/outqueue.c, so only an RTT sample is lost. > /* free auth if no more chunks, or add it back */ > if (list_empty(&packet->chunk_list)) > sctp_chunk_free(packet->auth); > diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c > index 708fa07d5fffc..bb89c9b52e0b0 100644 > --- a/net/sctp/sm_statefuns.c > +++ b/net/sctp/sm_statefuns.c > @@ -4455,9 +4455,12 @@ static enum sctp_ierror sctp_sf_authenticate( > > memset(digest, 0, sig_len); > > - sctp_auth_calculate_hmac(asoc, chunk->skb, > - (struct sctp_auth_chunk *)chunk->chunk_hdr, > - sh_key, GFP_ATOMIC); > + if (sctp_auth_calculate_hmac(asoc, chunk->skb, > + (struct sctp_auth_chunk *)chunk->chunk_hdr, > + sh_key, GFP_ATOMIC)) { > + kfree(save_digest); > + return SCTP_IERROR_NOMEM; > + } [Severity: Low] [Finding: main-2-2] [Sources: opus-5] Does the new SCTP_IERROR_NOMEM reach both callers of sctp_sf_authenticate()? sctp_sf_eat_auth() has an explicit arm for it: case SCTP_IERROR_NOMEM: return SCTP_DISPOSITION_NOMEM; but sctp_auth_chunk_verify() still collapses everything into a bool: return sctp_sf_authenticate(asoc, &auth) == SCTP_IERROR_NO_ERROR; so on the delayed COOKIE ECHO path in sctp_sf_do_5_1D_ce() an allocation failure is still handled as an authentication failure: if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) { sctp_association_free(new_asoc); return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands); } The same helper is used by the ASCONF and reconf paths. Isn't this the NOMEM versus BAD_SIG conflation the changelog says the patch removes, and should sctp_auth_chunk_verify() be converted to return the ierror so all callers see the distinction? -- pw-bot: cr ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v2] sctp: auth: propagate HMAC calculation errors to callers 2026-07-30 6:42 [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers luoqing 2026-08-04 1:50 ` Jakub Kicinski @ 2026-08-04 6:50 ` luoqing 2026-08-04 8:58 ` [PATCH net v3] " luoqing 1 sibling, 1 reply; 5+ messages in thread From: luoqing @ 2026-08-04 6:50 UTC (permalink / raw) To: l1138897701 Cc: davem, dsahern, edumazet, horms, idosch, kuba, linux-kernel, netdev, pabeni From: Qing Luo <luoqing@kylinos.cn> sctp_auth_calculate_hmac() can fail when building the association secret under memory pressure, but its void return silently leaves the HMAC digest zeroed. On the receive path, sctp_sf_authenticate() compares this zeroed digest against the peer-supplied one using crypto_memneq(), potentially accepting an all-zero HMAC from the peer if the allocation failed. A peer can reach this path with a configured but non-active shared key id, so association setup is affected too. Although the allocation failure itself is not attacker controlled, the incorrect acceptance is a security issue. Fix this by making sctp_auth_calculate_hmac() return int: - sctp_sf_authenticate() returns SCTP_IERROR_NOMEM instead of accepting a zero HMAC. - sctp_auth_chunk_verify() propagates the ierror so delayed COOKIE ECHO and other paths see NOMEM vs BAD_SIG correctly. - sctp_packet_pack() drops the packet on failure instead of transmitting a zeroed HMAC that the peer would reject. Update the declaration in auth.h accordingly. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Cc: stable@vger.kernel.org Assisted-by: LLM:code-review Signed-off-by: Qing Luo <luoqing@kylinos.cn> --- include/net/sctp/auth.h | 6 ++--- net/sctp/auth.c | 10 ++++---- net/sctp/output.c | 12 +++++++--- net/sctp/sm_statefuns.c | 51 ++++++++++++++++++++++++++++++----------- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h index 6f2cd562b1de..eeb3297fe97d 100644 --- a/include/net/sctp/auth.h +++ b/include/net/sctp/auth.h @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, const struct sctp_association *asoc); int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc); -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp); +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp); void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); diff --git a/net/sctp/auth.c b/net/sctp/auth.c index c901d373af80..6de66f56c41c 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -613,9 +613,9 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) * zero (as shown in Figure 6) followed by all chunks that are placed * after the AUTH chunk in the SCTP packet. */ -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp) +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp) { struct sctp_auth_bytes *asoc_key; __u16 key_id, hmac_id; @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, /* ep_key can't be NULL here */ asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); if (!asoc_key) - return; + return -ENOMEM; free_key = 1; } @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, if (free_key) sctp_auth_key_put(asoc_key); + + return 0; } /* API Helpers */ diff --git a/net/sctp/output.c b/net/sctp/output.c index 23e96305cad7..3d7ead9d40e1 100644 --- a/net/sctp/output.c +++ b/net/sctp/output.c @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, } if (auth) { - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, - packet->auth->shkey, gfp); + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, + packet->auth->shkey, gfp)) { + sctp_chunk_free(packet->auth); + packet->auth = NULL; + if (gso) + kfree_skb(nskb); + return -ENOMEM; + } /* free auth if no more chunks, or add it back */ if (list_empty(&packet->chunk_list)) sctp_chunk_free(packet->auth); @@ -619,7 +625,7 @@ int sctp_packet_transmit(struct sctp_packet *packet, gfp_t gfp) /* pack up chunks */ pkt_count = sctp_packet_pack(packet, head, gso, gfp); - if (!pkt_count) { + if (pkt_count <= 0) { kfree_skb(head); goto out; } diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 708fa07d5fff..e19881c90b49 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -637,13 +637,17 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net, return SCTP_DISPOSITION_CONSUME; } -static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, - const struct sctp_association *asoc) +static enum sctp_ierror sctp_auth_chunk_verify(struct net *net, + struct sctp_chunk *chunk, + const struct sctp_association *asoc) { struct sctp_chunk auth; - if (!chunk->auth_chunk) - return !sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc); + if (!chunk->auth_chunk) { + if (sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc)) + return SCTP_IERROR_BAD_SIG; + return SCTP_IERROR_NO_ERROR; + } /* SCTP-AUTH: auth_chunk pointer is only set when the cookie-echo * is supposed to be authenticated and we have to do delayed @@ -654,7 +658,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, /* Make sure that we and the peer are AUTH capable */ if (!net->sctp.auth_enable || !asoc->peer.auth_capable) - return false; + return SCTP_IERROR_BAD_SIG; /* set-up our fake chunk so that we can process it */ auth.skb = chunk->auth_chunk; @@ -666,7 +670,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, skb_pull(chunk->auth_chunk, sizeof(struct sctp_chunkhdr)); auth.transport = chunk->transport; - return sctp_sf_authenticate(asoc, &auth) == SCTP_IERROR_NO_ERROR; + return sctp_sf_authenticate(asoc, &auth); } /* @@ -826,8 +830,11 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, if (error) goto nomem_init; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) { + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { sctp_association_free(new_asoc); + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands); } @@ -1889,6 +1896,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_a( { struct sctp_init_chunk *peer_init; enum sctp_disposition disposition; + enum sctp_ierror error; struct sctp_ulpevent *ev; struct sctp_chunk *repl; struct sctp_chunk *err; @@ -1904,8 +1912,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_a( if (sctp_auth_asoc_init_active_key(new_asoc, GFP_ATOMIC)) goto nomem; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } /* Make sure no new addresses are being added during the * restart. Though this is a pretty complicated attack @@ -2011,6 +2023,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_b( struct sctp_association *new_asoc) { struct sctp_init_chunk *peer_init; + enum sctp_ierror error; struct sctp_chunk *repl; /* new_asoc is a brand-new association, so these are not yet @@ -2024,8 +2037,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_b( if (sctp_auth_asoc_init_active_key(new_asoc, GFP_ATOMIC)) goto nomem; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE, SCTP_STATE(SCTP_STATE_ESTABLISHED)); @@ -2118,6 +2135,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_d( struct sctp_association *new_asoc) { struct sctp_ulpevent *ev = NULL, *ai_ev = NULL, *auth_ev = NULL; + enum sctp_ierror error; struct sctp_chunk *repl; /* Clarification from Implementor's Guide: @@ -2127,8 +2145,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_d( * a COOKIE ACK. */ - if (!sctp_auth_chunk_verify(net, chunk, asoc)) + error = sctp_auth_chunk_verify(net, chunk, asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } /* Don't accidentally move back into established state. */ if (asoc->state < SCTP_STATE_ESTABLISHED) { @@ -4455,9 +4477,12 @@ static enum sctp_ierror sctp_sf_authenticate( memset(digest, 0, sig_len); - sctp_auth_calculate_hmac(asoc, chunk->skb, - (struct sctp_auth_chunk *)chunk->chunk_hdr, - sh_key, GFP_ATOMIC); + if (sctp_auth_calculate_hmac(asoc, chunk->skb, + (struct sctp_auth_chunk *)chunk->chunk_hdr, + sh_key, GFP_ATOMIC)) { + kfree(save_digest); + return SCTP_IERROR_NOMEM; + } /* Discard the packet if the digests do not match */ if (crypto_memneq(save_digest, digest, sig_len)) { -- 2.25.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net v3] sctp: auth: propagate HMAC calculation errors to callers 2026-08-04 6:50 ` [PATCH net v2] " luoqing @ 2026-08-04 8:58 ` luoqing 2026-08-05 18:06 ` Xin Long 0 siblings, 1 reply; 5+ messages in thread From: luoqing @ 2026-08-04 8:58 UTC (permalink / raw) To: l1138897701 Cc: davem, edumazet, horms, kuba, pabeni, marcelo.leitner, lucien.xin, vladislav.yasevich, linux-sctp, netdev, linux-kernel From: Qing Luo <luoqing@kylinos.cn> sctp_auth_calculate_hmac() can fail when building the association secret under memory pressure, but its void return silently leaves the HMAC digest zeroed. On the receive path, sctp_sf_authenticate() compares this zeroed digest against the peer-supplied one using crypto_memneq(), potentially accepting an all-zero HMAC from the peer if the allocation failed. A peer can reach this path with a configured but non-active shared key id, so association setup is affected too. Although the allocation failure itself is not attacker controlled, the incorrect acceptance is a security issue. Fix this by making sctp_auth_calculate_hmac() return int: - sctp_sf_authenticate() returns SCTP_IERROR_NOMEM instead of accepting a zero HMAC. - sctp_auth_chunk_verify() propagates the ierror so delayed COOKIE ECHO and other paths see NOMEM vs BAD_SIG correctly. - sctp_packet_pack() drops the packet on failure instead of transmitting a zeroed HMAC that the peer would reject. Update the declaration in auth.h accordingly. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Cc: stable@vger.kernel.org Assisted-by: LLM:code-review Signed-off-by: Qing Luo <luoqing@kylinos.cn> --- include/net/sctp/auth.h | 6 ++--- net/sctp/auth.c | 10 ++++---- net/sctp/output.c | 12 +++++++--- net/sctp/sm_statefuns.c | 51 ++++++++++++++++++++++++++++++----------- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h index 6f2cd562b1de..eeb3297fe97d 100644 --- a/include/net/sctp/auth.h +++ b/include/net/sctp/auth.h @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, const struct sctp_association *asoc); int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc); -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp); +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp); void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); diff --git a/net/sctp/auth.c b/net/sctp/auth.c index c901d373af80..6de66f56c41c 100644 --- a/net/sctp/auth.c +++ b/net/sctp/auth.c @@ -613,9 +613,9 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) * zero (as shown in Figure 6) followed by all chunks that are placed * after the AUTH chunk in the SCTP packet. */ -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, - struct sk_buff *skb, struct sctp_auth_chunk *auth, - struct sctp_shared_key *ep_key, gfp_t gfp) +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, + struct sk_buff *skb, struct sctp_auth_chunk *auth, + struct sctp_shared_key *ep_key, gfp_t gfp) { struct sctp_auth_bytes *asoc_key; __u16 key_id, hmac_id; @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, /* ep_key can't be NULL here */ asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); if (!asoc_key) - return; + return -ENOMEM; free_key = 1; } @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, if (free_key) sctp_auth_key_put(asoc_key); + + return 0; } /* API Helpers */ diff --git a/net/sctp/output.c b/net/sctp/output.c index 23e96305cad7..3d7ead9d40e1 100644 --- a/net/sctp/output.c +++ b/net/sctp/output.c @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, } if (auth) { - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, - packet->auth->shkey, gfp); + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, + packet->auth->shkey, gfp)) { + sctp_chunk_free(packet->auth); + packet->auth = NULL; + if (gso) + kfree_skb(nskb); + return -ENOMEM; + } /* free auth if no more chunks, or add it back */ if (list_empty(&packet->chunk_list)) sctp_chunk_free(packet->auth); @@ -619,7 +625,7 @@ int sctp_packet_transmit(struct sctp_packet *packet, gfp_t gfp) /* pack up chunks */ pkt_count = sctp_packet_pack(packet, head, gso, gfp); - if (!pkt_count) { + if (pkt_count <= 0) { kfree_skb(head); goto out; } diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 708fa07d5fff..e19881c90b49 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -637,13 +637,17 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net, return SCTP_DISPOSITION_CONSUME; } -static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, - const struct sctp_association *asoc) +static enum sctp_ierror sctp_auth_chunk_verify(struct net *net, + struct sctp_chunk *chunk, + const struct sctp_association *asoc) { struct sctp_chunk auth; - if (!chunk->auth_chunk) - return !sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc); + if (!chunk->auth_chunk) { + if (sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc)) + return SCTP_IERROR_BAD_SIG; + return SCTP_IERROR_NO_ERROR; + } /* SCTP-AUTH: auth_chunk pointer is only set when the cookie-echo * is supposed to be authenticated and we have to do delayed @@ -654,7 +658,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, /* Make sure that we and the peer are AUTH capable */ if (!net->sctp.auth_enable || !asoc->peer.auth_capable) - return false; + return SCTP_IERROR_BAD_SIG; /* set-up our fake chunk so that we can process it */ auth.skb = chunk->auth_chunk; @@ -666,7 +670,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, skb_pull(chunk->auth_chunk, sizeof(struct sctp_chunkhdr)); auth.transport = chunk->transport; - return sctp_sf_authenticate(asoc, &auth) == SCTP_IERROR_NO_ERROR; + return sctp_sf_authenticate(asoc, &auth); } /* @@ -826,8 +830,11 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, if (error) goto nomem_init; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) { + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { sctp_association_free(new_asoc); + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands); } @@ -1889,6 +1896,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_a( { struct sctp_init_chunk *peer_init; enum sctp_disposition disposition; + enum sctp_ierror error; struct sctp_ulpevent *ev; struct sctp_chunk *repl; struct sctp_chunk *err; @@ -1904,8 +1912,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_a( if (sctp_auth_asoc_init_active_key(new_asoc, GFP_ATOMIC)) goto nomem; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } /* Make sure no new addresses are being added during the * restart. Though this is a pretty complicated attack @@ -2011,6 +2023,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_b( struct sctp_association *new_asoc) { struct sctp_init_chunk *peer_init; + enum sctp_ierror error; struct sctp_chunk *repl; /* new_asoc is a brand-new association, so these are not yet @@ -2024,8 +2037,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_b( if (sctp_auth_asoc_init_active_key(new_asoc, GFP_ATOMIC)) goto nomem; - if (!sctp_auth_chunk_verify(net, chunk, new_asoc)) + error = sctp_auth_chunk_verify(net, chunk, new_asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE, SCTP_STATE(SCTP_STATE_ESTABLISHED)); @@ -2118,6 +2135,7 @@ static enum sctp_disposition sctp_sf_do_dupcook_d( struct sctp_association *new_asoc) { struct sctp_ulpevent *ev = NULL, *ai_ev = NULL, *auth_ev = NULL; + enum sctp_ierror error; struct sctp_chunk *repl; /* Clarification from Implementor's Guide: @@ -2127,8 +2145,12 @@ static enum sctp_disposition sctp_sf_do_dupcook_d( * a COOKIE ACK. */ - if (!sctp_auth_chunk_verify(net, chunk, asoc)) + error = sctp_auth_chunk_verify(net, chunk, asoc); + if (error != SCTP_IERROR_NO_ERROR) { + if (error == SCTP_IERROR_NOMEM) + return SCTP_DISPOSITION_NOMEM; return SCTP_DISPOSITION_DISCARD; + } /* Don't accidentally move back into established state. */ if (asoc->state < SCTP_STATE_ESTABLISHED) { @@ -4455,9 +4477,12 @@ static enum sctp_ierror sctp_sf_authenticate( memset(digest, 0, sig_len); - sctp_auth_calculate_hmac(asoc, chunk->skb, - (struct sctp_auth_chunk *)chunk->chunk_hdr, - sh_key, GFP_ATOMIC); + if (sctp_auth_calculate_hmac(asoc, chunk->skb, + (struct sctp_auth_chunk *)chunk->chunk_hdr, + sh_key, GFP_ATOMIC)) { + kfree(save_digest); + return SCTP_IERROR_NOMEM; + } /* Discard the packet if the digests do not match */ if (crypto_memneq(save_digest, digest, sig_len)) { -- 2.25.1 v3: Add missing SCTP maintainers and sctp mailing list, no code changes ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net v3] sctp: auth: propagate HMAC calculation errors to callers 2026-08-04 8:58 ` [PATCH net v3] " luoqing @ 2026-08-05 18:06 ` Xin Long 0 siblings, 0 replies; 5+ messages in thread From: Xin Long @ 2026-08-05 18:06 UTC (permalink / raw) To: luoqing Cc: davem, edumazet, horms, kuba, pabeni, marcelo.leitner, vladislav.yasevich, linux-sctp, netdev, linux-kernel On Tue, Aug 4, 2026 at 4:59 AM luoqing <l1138897701@163.com> wrote: > > From: Qing Luo <luoqing@kylinos.cn> > > sctp_auth_calculate_hmac() can fail when building the association secret > under memory pressure, but its void return silently leaves the HMAC digest > zeroed. On the receive path, sctp_sf_authenticate() compares this zeroed > digest against the peer-supplied one using crypto_memneq(), potentially > accepting an all-zero HMAC from the peer if the allocation failed. A peer > can reach this path with a configured but non-active shared key id, so > association setup is affected too. Although the allocation failure itself > is not attacker controlled, the incorrect acceptance is a security issue. > > Fix this by making sctp_auth_calculate_hmac() return int: > - sctp_sf_authenticate() returns SCTP_IERROR_NOMEM instead of accepting > a zero HMAC. > - sctp_auth_chunk_verify() propagates the ierror so delayed COOKIE ECHO > and other paths see NOMEM vs BAD_SIG correctly. > - sctp_packet_pack() drops the packet on failure instead of transmitting > a zeroed HMAC that the peer would reject. > > Update the declaration in auth.h accordingly. > > Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") > Cc: stable@vger.kernel.org > Assisted-by: LLM:code-review > Signed-off-by: Qing Luo <luoqing@kylinos.cn> > --- Please add a revision changelog here describing the changes since the previous version, similar to: https://lore.kernel.org/netdev/20260729160028.54546-1-baul.lee@xbow.com/ > include/net/sctp/auth.h | 6 ++--- > net/sctp/auth.c | 10 ++++---- > net/sctp/output.c | 12 +++++++--- > net/sctp/sm_statefuns.c | 51 ++++++++++++++++++++++++++++++----------- > 4 files changed, 56 insertions(+), 23 deletions(-) > > diff --git a/include/net/sctp/auth.h b/include/net/sctp/auth.h > index 6f2cd562b1de..eeb3297fe97d 100644 > --- a/include/net/sctp/auth.h > +++ b/include/net/sctp/auth.h > @@ -83,9 +83,9 @@ int sctp_auth_send_cid(enum sctp_cid chunk, > const struct sctp_association *asoc); > int sctp_auth_recv_cid(enum sctp_cid chunk, > const struct sctp_association *asoc); > -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > - struct sk_buff *skb, struct sctp_auth_chunk *auth, > - struct sctp_shared_key *ep_key, gfp_t gfp); > +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, > + struct sk_buff *skb, struct sctp_auth_chunk *auth, > + struct sctp_shared_key *ep_key, gfp_t gfp); > void sctp_auth_shkey_release(struct sctp_shared_key *sh_key); > void sctp_auth_shkey_hold(struct sctp_shared_key *sh_key); > > diff --git a/net/sctp/auth.c b/net/sctp/auth.c > index c901d373af80..6de66f56c41c 100644 > --- a/net/sctp/auth.c > +++ b/net/sctp/auth.c > @@ -613,9 +613,9 @@ int sctp_auth_recv_cid(enum sctp_cid chunk, const struct sctp_association *asoc) > * zero (as shown in Figure 6) followed by all chunks that are placed > * after the AUTH chunk in the SCTP packet. > */ > -void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > - struct sk_buff *skb, struct sctp_auth_chunk *auth, > - struct sctp_shared_key *ep_key, gfp_t gfp) > +int sctp_auth_calculate_hmac(const struct sctp_association *asoc, > + struct sk_buff *skb, struct sctp_auth_chunk *auth, > + struct sctp_shared_key *ep_key, gfp_t gfp) > { > struct sctp_auth_bytes *asoc_key; > __u16 key_id, hmac_id; > @@ -636,7 +636,7 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > /* ep_key can't be NULL here */ > asoc_key = sctp_auth_asoc_create_secret(asoc, ep_key, gfp); > if (!asoc_key) > - return; > + return -ENOMEM; > > free_key = 1; > } > @@ -654,6 +654,8 @@ void sctp_auth_calculate_hmac(const struct sctp_association *asoc, > > if (free_key) > sctp_auth_key_put(asoc_key); > + > + return 0; > } > > /* API Helpers */ > diff --git a/net/sctp/output.c b/net/sctp/output.c > index 23e96305cad7..3d7ead9d40e1 100644 > --- a/net/sctp/output.c > +++ b/net/sctp/output.c > @@ -517,8 +517,14 @@ static int sctp_packet_pack(struct sctp_packet *packet, > } > > if (auth) { > - sctp_auth_calculate_hmac(tp->asoc, nskb, auth, > - packet->auth->shkey, gfp); > + if (sctp_auth_calculate_hmac(tp->asoc, nskb, auth, > + packet->auth->shkey, gfp)) { > + sctp_chunk_free(packet->auth); > + packet->auth = NULL; > + if (gso) > + kfree_skb(nskb); > + return -ENOMEM; Returning 0 here is fine, as its only caller, sctp_packet_transmit(), currently always returns 0. sctp_packet_pack(), on the other hand, returns the number of packets it builds. If you'd like to improve the return value for sctp_packet_transmit(), that can be done in a separate patch targeting net-next. > + } > /* free auth if no more chunks, or add it back */ > if (list_empty(&packet->chunk_list)) > sctp_chunk_free(packet->auth); > @@ -619,7 +625,7 @@ int sctp_packet_transmit(struct sctp_packet *packet, gfp_t gfp) > > /* pack up chunks */ > pkt_count = sctp_packet_pack(packet, head, gso, gfp); > - if (!pkt_count) { > + if (pkt_count <= 0) { > kfree_skb(head); > goto out; > } > diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c > index 708fa07d5fff..e19881c90b49 100644 > --- a/net/sctp/sm_statefuns.c > +++ b/net/sctp/sm_statefuns.c > @@ -637,13 +637,17 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net, > return SCTP_DISPOSITION_CONSUME; > } > > -static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, > - const struct sctp_association *asoc) > +static enum sctp_ierror sctp_auth_chunk_verify(struct net *net, > + struct sctp_chunk *chunk, > + const struct sctp_association *asoc) > { > struct sctp_chunk auth; > > - if (!chunk->auth_chunk) > - return !sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc); > + if (!chunk->auth_chunk) { > + if (sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc)) > + return SCTP_IERROR_BAD_SIG; > + return SCTP_IERROR_NO_ERROR; > + } > > /* SCTP-AUTH: auth_chunk pointer is only set when the cookie-echo > * is supposed to be authenticated and we have to do delayed > @@ -654,7 +658,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, > > /* Make sure that we and the peer are AUTH capable */ > if (!net->sctp.auth_enable || !asoc->peer.auth_capable) > - return false; > + return SCTP_IERROR_BAD_SIG; > > /* set-up our fake chunk so that we can process it */ > auth.skb = chunk->auth_chunk; > @@ -666,7 +670,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk, > skb_pull(chunk->auth_chunk, sizeof(struct sctp_chunkhdr)); > auth.transport = chunk->transport; > > - return sctp_sf_authenticate(asoc, &auth) == SCTP_IERROR_NO_ERROR; > + return sctp_sf_authenticate(asoc, &auth); > } > Please drop the change to sctp_auth_chunk_verify(). This patch is intended as a fix, and code improvements should be submitted separately to net-next. Thanks. ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-05 18:07 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-30 6:42 [PATCH net-next] sctp: auth: propagate HMAC calculation errors to callers luoqing 2026-08-04 1:50 ` Jakub Kicinski 2026-08-04 6:50 ` [PATCH net v2] " luoqing 2026-08-04 8:58 ` [PATCH net v3] " luoqing 2026-08-05 18:06 ` Xin Long
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox