Skip to content

Commit

Permalink
net: sctp: cache auth_enable per endpoint
Browse files Browse the repository at this point in the history
Currently, it is possible to create an SCTP socket, then switch
auth_enable via sysctl setting to 1 and crash the system on connect:

Oops[#1]:
CPU: 0 PID: 0 Comm: swapper Not tainted 3.14.1-mipsgit-20140415 #1
task: ffffffff8056ce80 ti: ffffffff8055c000 task.ti: ffffffff8055c000
[...]
Call Trace:
[<ffffffff8043c4e8>] sctp_auth_asoc_set_default_hmac+0x68/0x80
[<ffffffff8042b300>] sctp_process_init+0x5e0/0x8a4
[<ffffffff8042188c>] sctp_sf_do_5_1B_init+0x234/0x34c
[<ffffffff804228c8>] sctp_do_sm+0xb4/0x1e8
[<ffffffff80425a08>] sctp_endpoint_bh_rcv+0x1c4/0x214
[<ffffffff8043af68>] sctp_rcv+0x588/0x630
[<ffffffff8043e8e8>] sctp6_rcv+0x10/0x24
[<ffffffff803acb50>] ip6_input+0x2c0/0x440
[<ffffffff8030fc00>] __netif_receive_skb_core+0x4a8/0x564
[<ffffffff80310650>] process_backlog+0xb4/0x18c
[<ffffffff80313cbc>] net_rx_action+0x12c/0x210
[<ffffffff80034254>] __do_softirq+0x17c/0x2ac
[<ffffffff800345e0>] irq_exit+0x54/0xb0
[<ffffffff800075a4>] ret_from_irq+0x0/0x4
[<ffffffff800090ec>] rm7k_wait_irqoff+0x24/0x48
[<ffffffff8005e388>] cpu_startup_entry+0xc0/0x148
[<ffffffff805a88b0>] start_kernel+0x37c/0x398
Code: dd0900b8  000330f8  0126302d <dcc60000> 50c0fff1  0047182a  a48306a0
03e00008  00000000
---[ end trace b530b0551467f2fd ]---
Kernel panic - not syncing: Fatal exception in interrupt

What happens while auth_enable=0 in that case is, that
ep->auth_hmacs is initialized to NULL in sctp_auth_init_hmacs()
when endpoint is being created.

After that point, if an admin switches over to auth_enable=1,
the machine can crash due to NULL pointer dereference during
reception of an INIT chunk. When we enter sctp_process_init()
via sctp_sf_do_5_1B_init() in order to respond to an INIT chunk,
the INIT verification succeeds and while we walk and process
all INIT params via sctp_process_param() we find that
net->sctp.auth_enable is set, therefore do not fall through,
but invoke sctp_auth_asoc_set_default_hmac() instead, and thus,
dereference what we have set to NULL during endpoint
initialization phase.

The fix is to make auth_enable immutable by caching its value
during endpoint initialization, so that its original value is
being carried along until destruction. The bug seems to originate
from the very first days.

Fix in joint work with Daniel Borkmann.

Reported-by: Joshua Kinard <kumba@gentoo.org>
Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Acked-by: Neil Horman <nhorman@tuxdriver.com>
Tested-by: Joshua Kinard <kumba@gentoo.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Vlad Yasevich authored and davem330 committed Apr 18, 2014
1 parent 5a292f7 commit b14878c
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 60 deletions.
4 changes: 3 additions & 1 deletion include/net/sctp/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@ struct sctp_endpoint {
/* SCTP-AUTH: endpoint shared keys */
struct list_head endpoint_shared_keys;
__u16 active_key_id;
__u8 auth_enable;
};

/* Recover the outter endpoint structure. */
Expand Down Expand Up @@ -1269,7 +1270,8 @@ struct sctp_endpoint *sctp_endpoint_is_match(struct sctp_endpoint *,
int sctp_has_association(struct net *net, const union sctp_addr *laddr,
const union sctp_addr *paddr);

int sctp_verify_init(struct net *net, const struct sctp_association *asoc,
int sctp_verify_init(struct net *net, const struct sctp_endpoint *ep,
const struct sctp_association *asoc,
sctp_cid_t, sctp_init_chunk_t *peer_init,
struct sctp_chunk *chunk, struct sctp_chunk **err_chunk);
int sctp_process_init(struct sctp_association *, struct sctp_chunk *chunk,
Expand Down
17 changes: 6 additions & 11 deletions net/sctp/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,13 @@ int sctp_auth_asoc_copy_shkeys(const struct sctp_endpoint *ep,
*/
int sctp_auth_asoc_init_active_key(struct sctp_association *asoc, gfp_t gfp)
{
struct net *net = sock_net(asoc->base.sk);
struct sctp_auth_bytes *secret;
struct sctp_shared_key *ep_key;

/* If we don't support AUTH, or peer is not capable
* we don't need to do anything.
*/
if (!net->sctp.auth_enable || !asoc->peer.auth_capable)
if (!asoc->ep->auth_enable || !asoc->peer.auth_capable)
return 0;

/* If the key_id is non-zero and we couldn't find an
Expand Down Expand Up @@ -440,16 +439,16 @@ struct sctp_shared_key *sctp_auth_get_shkey(
*/
int sctp_auth_init_hmacs(struct sctp_endpoint *ep, gfp_t gfp)
{
struct net *net = sock_net(ep->base.sk);
struct crypto_hash *tfm = NULL;
__u16 id;

/* if the transforms are already allocted, we are done */
if (!net->sctp.auth_enable) {
/* If AUTH extension is disabled, we are done */
if (!ep->auth_enable) {
ep->auth_hmacs = NULL;
return 0;
}

/* If the transforms are already allocated, we are done */
if (ep->auth_hmacs)
return 0;

Expand Down Expand Up @@ -665,12 +664,10 @@ static int __sctp_auth_cid(sctp_cid_t chunk, struct sctp_chunks_param *param)
/* Check if peer requested that this chunk is authenticated */
int sctp_auth_send_cid(sctp_cid_t chunk, const struct sctp_association *asoc)
{
struct net *net;
if (!asoc)
return 0;

net = sock_net(asoc->base.sk);
if (!net->sctp.auth_enable || !asoc->peer.auth_capable)
if (!asoc->ep->auth_enable || !asoc->peer.auth_capable)
return 0;

return __sctp_auth_cid(chunk, asoc->peer.peer_chunks);
Expand All @@ -679,12 +676,10 @@ int sctp_auth_send_cid(sctp_cid_t chunk, const struct sctp_association *asoc)
/* Check if we requested that peer authenticate this chunk. */
int sctp_auth_recv_cid(sctp_cid_t chunk, const struct sctp_association *asoc)
{
struct net *net;
if (!asoc)
return 0;

net = sock_net(asoc->base.sk);
if (!net->sctp.auth_enable)
if (!asoc->ep->auth_enable)
return 0;

return __sctp_auth_cid(chunk,
Expand Down
3 changes: 2 additions & 1 deletion net/sctp/endpointola.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ static struct sctp_endpoint *sctp_endpoint_init(struct sctp_endpoint *ep,
if (!ep->digest)
return NULL;

if (net->sctp.auth_enable) {
ep->auth_enable = net->sctp.auth_enable;
if (ep->auth_enable) {
/* Allocate space for HMACS and CHUNKS authentication
* variables. There are arrays that we encode directly
* into parameters to make the rest of the operations easier.
Expand Down
32 changes: 17 additions & 15 deletions net/sctp/sm_make_chunk.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ struct sctp_chunk *sctp_make_init(const struct sctp_association *asoc,
gfp_t gfp, int vparam_len)
{
struct net *net = sock_net(asoc->base.sk);
struct sctp_endpoint *ep = asoc->ep;
sctp_inithdr_t init;
union sctp_params addrs;
size_t chunksize;
Expand Down Expand Up @@ -278,7 +279,7 @@ struct sctp_chunk *sctp_make_init(const struct sctp_association *asoc,
chunksize += vparam_len;

/* Account for AUTH related parameters */
if (net->sctp.auth_enable) {
if (ep->auth_enable) {
/* Add random parameter length*/
chunksize += sizeof(asoc->c.auth_random);

Expand Down Expand Up @@ -363,7 +364,7 @@ struct sctp_chunk *sctp_make_init(const struct sctp_association *asoc,
}

/* Add SCTP-AUTH chunks to the parameter list */
if (net->sctp.auth_enable) {
if (ep->auth_enable) {
sctp_addto_chunk(retval, sizeof(asoc->c.auth_random),
asoc->c.auth_random);
if (auth_hmacs)
Expand Down Expand Up @@ -2010,7 +2011,7 @@ static void sctp_process_ext_param(struct sctp_association *asoc,
/* if the peer reports AUTH, assume that he
* supports AUTH.
*/
if (net->sctp.auth_enable)
if (asoc->ep->auth_enable)
asoc->peer.auth_capable = 1;
break;
case SCTP_CID_ASCONF:
Expand Down Expand Up @@ -2102,6 +2103,7 @@ static sctp_ierror_t sctp_process_unk_param(const struct sctp_association *asoc,
* SCTP_IERROR_NO_ERROR - continue with the chunk
*/
static sctp_ierror_t sctp_verify_param(struct net *net,
const struct sctp_endpoint *ep,
const struct sctp_association *asoc,
union sctp_params param,
sctp_cid_t cid,
Expand Down Expand Up @@ -2152,7 +2154,7 @@ static sctp_ierror_t sctp_verify_param(struct net *net,
goto fallthrough;

case SCTP_PARAM_RANDOM:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fallthrough;

/* SCTP-AUTH: Secion 6.1
Expand All @@ -2169,7 +2171,7 @@ static sctp_ierror_t sctp_verify_param(struct net *net,
break;

case SCTP_PARAM_CHUNKS:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fallthrough;

/* SCTP-AUTH: Section 3.2
Expand All @@ -2185,7 +2187,7 @@ static sctp_ierror_t sctp_verify_param(struct net *net,
break;

case SCTP_PARAM_HMAC_ALGO:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fallthrough;

hmacs = (struct sctp_hmac_algo_param *)param.p;
Expand Down Expand Up @@ -2220,10 +2222,9 @@ static sctp_ierror_t sctp_verify_param(struct net *net,
}

/* Verify the INIT packet before we process it. */
int sctp_verify_init(struct net *net, const struct sctp_association *asoc,
sctp_cid_t cid,
sctp_init_chunk_t *peer_init,
struct sctp_chunk *chunk,
int sctp_verify_init(struct net *net, const struct sctp_endpoint *ep,
const struct sctp_association *asoc, sctp_cid_t cid,
sctp_init_chunk_t *peer_init, struct sctp_chunk *chunk,
struct sctp_chunk **errp)
{
union sctp_params param;
Expand Down Expand Up @@ -2264,8 +2265,8 @@ int sctp_verify_init(struct net *net, const struct sctp_association *asoc,

/* Verify all the variable length parameters */
sctp_walk_params(param, peer_init, init_hdr.params) {

result = sctp_verify_param(net, asoc, param, cid, chunk, errp);
result = sctp_verify_param(net, ep, asoc, param, cid,
chunk, errp);
switch (result) {
case SCTP_IERROR_ABORT:
case SCTP_IERROR_NOMEM:
Expand Down Expand Up @@ -2497,6 +2498,7 @@ static int sctp_process_param(struct sctp_association *asoc,
struct sctp_af *af;
union sctp_addr_param *addr_param;
struct sctp_transport *t;
struct sctp_endpoint *ep = asoc->ep;

/* We maintain all INIT parameters in network byte order all the
* time. This allows us to not worry about whether the parameters
Expand Down Expand Up @@ -2636,7 +2638,7 @@ static int sctp_process_param(struct sctp_association *asoc,
goto fall_through;

case SCTP_PARAM_RANDOM:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fall_through;

/* Save peer's random parameter */
Expand All @@ -2649,7 +2651,7 @@ static int sctp_process_param(struct sctp_association *asoc,
break;

case SCTP_PARAM_HMAC_ALGO:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fall_through;

/* Save peer's HMAC list */
Expand All @@ -2665,7 +2667,7 @@ static int sctp_process_param(struct sctp_association *asoc,
break;

case SCTP_PARAM_CHUNKS:
if (!net->sctp.auth_enable)
if (!ep->auth_enable)
goto fall_through;

asoc->peer.peer_chunks = kmemdup(param.p,
Expand Down
6 changes: 3 additions & 3 deletions net/sctp/sm_statefuns.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ sctp_disposition_t sctp_sf_do_5_1B_init(struct net *net,

/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
if (!sctp_verify_init(net, asoc, chunk->chunk_hdr->type,
if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
(sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
&err_chunk)) {
/* This chunk contains fatal error. It is to be discarded.
Expand Down Expand Up @@ -524,7 +524,7 @@ sctp_disposition_t sctp_sf_do_5_1C_ack(struct net *net,

/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
if (!sctp_verify_init(net, asoc, chunk->chunk_hdr->type,
if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
(sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
&err_chunk)) {

Expand Down Expand Up @@ -1430,7 +1430,7 @@ static sctp_disposition_t sctp_sf_do_unexpected_init(

/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
if (!sctp_verify_init(net, asoc, chunk->chunk_hdr->type,
if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
(sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
&err_chunk)) {
/* This chunk contains fatal error. It is to be discarded.
Expand Down
Loading

0 comments on commit b14878c

Please sign in to comment.