mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Propagate the error code if key_alloc() fails. Don't return
success.
Fixes: 9d1d2b5934
("rxrpc: rxgk: Implement the yfs-rxgk security class (GSSAPI)")
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/Z_-P_1iLDWksH1ik@stanley.mountain
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
286 lines
7.6 KiB
C
286 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Application-specific bits for GSSAPI-based RxRPC security
|
|
*
|
|
* Copyright (C) 2025 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/net.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/key-type.h>
|
|
#include "ar-internal.h"
|
|
#include "rxgk_common.h"
|
|
|
|
/*
|
|
* Decode a default-style YFS ticket in a response and turn it into an
|
|
* rxrpc-type key.
|
|
*
|
|
* struct rxgk_key {
|
|
* afs_uint32 enctype;
|
|
* opaque key<>;
|
|
* };
|
|
*
|
|
* struct RXGK_AuthName {
|
|
* afs_int32 kind;
|
|
* opaque data<AUTHDATAMAX>;
|
|
* opaque display<AUTHPRINTABLEMAX>;
|
|
* };
|
|
*
|
|
* struct RXGK_Token {
|
|
* rxgk_key K0;
|
|
* RXGK_Level level;
|
|
* rxgkTime starttime;
|
|
* afs_int32 lifetime;
|
|
* afs_int32 bytelife;
|
|
* rxgkTime expirationtime;
|
|
* struct RXGK_AuthName identities<>;
|
|
* };
|
|
*/
|
|
int rxgk_yfs_decode_ticket(struct rxrpc_connection *conn, struct sk_buff *skb,
|
|
unsigned int ticket_offset, unsigned int ticket_len,
|
|
struct key **_key)
|
|
{
|
|
struct rxrpc_key_token *token;
|
|
const struct cred *cred = current_cred(); // TODO - use socket creds
|
|
struct key *key;
|
|
size_t pre_ticket_len, payload_len;
|
|
unsigned int klen, enctype;
|
|
void *payload, *ticket;
|
|
__be32 *t, *p, *q, tmp[2];
|
|
int ret;
|
|
|
|
_enter("");
|
|
|
|
/* Get the session key length */
|
|
ret = skb_copy_bits(skb, ticket_offset, tmp, sizeof(tmp));
|
|
if (ret < 0)
|
|
return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
|
|
rxgk_abort_resp_short_yfs_klen);
|
|
enctype = ntohl(tmp[0]);
|
|
klen = ntohl(tmp[1]);
|
|
|
|
if (klen > ticket_len - 10 * sizeof(__be32))
|
|
return rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
|
|
rxgk_abort_resp_short_yfs_key);
|
|
|
|
pre_ticket_len = ((5 + 14) * sizeof(__be32) +
|
|
xdr_round_up(klen) +
|
|
sizeof(__be32));
|
|
payload_len = pre_ticket_len + xdr_round_up(ticket_len);
|
|
|
|
payload = kzalloc(payload_len, GFP_NOFS);
|
|
if (!payload)
|
|
return -ENOMEM;
|
|
|
|
/* We need to fill out the XDR form for a key payload that we can pass
|
|
* to add_key(). Start by copying in the ticket so that we can parse
|
|
* it.
|
|
*/
|
|
ticket = payload + pre_ticket_len;
|
|
ret = skb_copy_bits(skb, ticket_offset, ticket, ticket_len);
|
|
if (ret < 0) {
|
|
ret = rxrpc_abort_conn(conn, skb, RXGK_INCONSISTENCY, -EPROTO,
|
|
rxgk_abort_resp_short_yfs_tkt);
|
|
goto error;
|
|
}
|
|
|
|
/* Fill out the form header. */
|
|
p = payload;
|
|
p[0] = htonl(0); /* Flags */
|
|
p[1] = htonl(1); /* len(cellname) */
|
|
p[2] = htonl(0x20000000); /* Cellname " " */
|
|
p[3] = htonl(1); /* #tokens */
|
|
p[4] = htonl(15 * sizeof(__be32) + xdr_round_up(klen) +
|
|
xdr_round_up(ticket_len)); /* Token len */
|
|
|
|
/* Now fill in the body. Most of this we can just scrape directly from
|
|
* the ticket.
|
|
*/
|
|
t = ticket + sizeof(__be32) * 2 + xdr_round_up(klen);
|
|
q = payload + 5 * sizeof(__be32);
|
|
q[0] = htonl(RXRPC_SECURITY_YFS_RXGK);
|
|
q[1] = t[1]; /* begintime - msw */
|
|
q[2] = t[2]; /* - lsw */
|
|
q[3] = t[5]; /* endtime - msw */
|
|
q[4] = t[6]; /* - lsw */
|
|
q[5] = 0; /* level - msw */
|
|
q[6] = t[0]; /* - lsw */
|
|
q[7] = 0; /* lifetime - msw */
|
|
q[8] = t[3]; /* - lsw */
|
|
q[9] = 0; /* bytelife - msw */
|
|
q[10] = t[4]; /* - lsw */
|
|
q[11] = 0; /* enctype - msw */
|
|
q[12] = htonl(enctype); /* - lsw */
|
|
q[13] = htonl(klen); /* Key length */
|
|
|
|
q += 14;
|
|
|
|
memcpy(q, ticket + sizeof(__be32) * 2, klen);
|
|
q += xdr_round_up(klen) / 4;
|
|
q[0] = htonl(ticket_len);
|
|
q++;
|
|
if (WARN_ON((unsigned long)q != (unsigned long)ticket)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
/* Ticket read in with skb_copy_bits above */
|
|
q += xdr_round_up(ticket_len) / 4;
|
|
if (WARN_ON((unsigned long)q - (unsigned long)payload != payload_len)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
/* Now turn that into a key. */
|
|
key = key_alloc(&key_type_rxrpc, "x",
|
|
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, // TODO: Use socket owner
|
|
KEY_USR_VIEW,
|
|
KEY_ALLOC_NOT_IN_QUOTA, NULL);
|
|
if (IS_ERR(key)) {
|
|
_leave(" = -ENOMEM [alloc %ld]", PTR_ERR(key));
|
|
ret = PTR_ERR(key);
|
|
goto error;
|
|
}
|
|
|
|
_debug("key %d", key_serial(key));
|
|
|
|
ret = key_instantiate_and_link(key, payload, payload_len, NULL, NULL);
|
|
if (ret < 0)
|
|
goto error_key;
|
|
|
|
token = key->payload.data[0];
|
|
token->no_leak_key = true;
|
|
*_key = key;
|
|
key = NULL;
|
|
ret = 0;
|
|
goto error;
|
|
|
|
error_key:
|
|
key_put(key);
|
|
error:
|
|
kfree_sensitive(payload);
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Extract the token and set up a session key from the details.
|
|
*
|
|
* struct RXGK_TokenContainer {
|
|
* afs_int32 kvno;
|
|
* afs_int32 enctype;
|
|
* opaque encrypted_token<>;
|
|
* };
|
|
*
|
|
* [tools.ietf.org/html/draft-wilkinson-afs3-rxgk-afs-08 sec 6.1]
|
|
*/
|
|
int rxgk_extract_token(struct rxrpc_connection *conn, struct sk_buff *skb,
|
|
unsigned int token_offset, unsigned int token_len,
|
|
struct key **_key)
|
|
{
|
|
const struct krb5_enctype *krb5;
|
|
const struct krb5_buffer *server_secret;
|
|
struct crypto_aead *token_enc = NULL;
|
|
struct key *server_key;
|
|
unsigned int ticket_offset, ticket_len;
|
|
u32 kvno, enctype;
|
|
int ret, ec;
|
|
|
|
struct {
|
|
__be32 kvno;
|
|
__be32 enctype;
|
|
__be32 token_len;
|
|
} container;
|
|
|
|
/* Decode the RXGK_TokenContainer object. This tells us which server
|
|
* key we should be using. We can then fetch the key, get the secret
|
|
* and set up the crypto to extract the token.
|
|
*/
|
|
if (skb_copy_bits(skb, token_offset, &container, sizeof(container)) < 0)
|
|
return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
|
|
rxgk_abort_resp_tok_short);
|
|
|
|
kvno = ntohl(container.kvno);
|
|
enctype = ntohl(container.enctype);
|
|
ticket_len = ntohl(container.token_len);
|
|
ticket_offset = token_offset + sizeof(container);
|
|
|
|
if (xdr_round_up(ticket_len) > token_len - 3 * 4)
|
|
return rxrpc_abort_conn(conn, skb, RXGK_PACKETSHORT, -EPROTO,
|
|
rxgk_abort_resp_tok_short);
|
|
|
|
_debug("KVNO %u", kvno);
|
|
_debug("ENC %u", enctype);
|
|
_debug("TLEN %u", ticket_len);
|
|
|
|
server_key = rxrpc_look_up_server_security(conn, skb, kvno, enctype);
|
|
if (IS_ERR(server_key))
|
|
goto cant_get_server_key;
|
|
|
|
down_read(&server_key->sem);
|
|
server_secret = (const void *)&server_key->payload.data[2];
|
|
ret = rxgk_set_up_token_cipher(server_secret, &token_enc, enctype, &krb5, GFP_NOFS);
|
|
up_read(&server_key->sem);
|
|
key_put(server_key);
|
|
if (ret < 0)
|
|
goto cant_get_token;
|
|
|
|
/* We can now decrypt and parse the token/ticket. This allows us to
|
|
* gain access to K0, from which we can derive the transport key and
|
|
* thence decode the authenticator.
|
|
*/
|
|
ret = rxgk_decrypt_skb(krb5, token_enc, skb,
|
|
&ticket_offset, &ticket_len, &ec);
|
|
crypto_free_aead(token_enc);
|
|
token_enc = NULL;
|
|
if (ret < 0)
|
|
return rxrpc_abort_conn(conn, skb, ec, ret,
|
|
rxgk_abort_resp_tok_dec);
|
|
|
|
ret = conn->security->default_decode_ticket(conn, skb, ticket_offset,
|
|
ticket_len, _key);
|
|
if (ret < 0)
|
|
goto cant_get_token;
|
|
|
|
_leave(" = 0");
|
|
return ret;
|
|
|
|
cant_get_server_key:
|
|
ret = PTR_ERR(server_key);
|
|
switch (ret) {
|
|
case -ENOMEM:
|
|
goto temporary_error;
|
|
case -ENOKEY:
|
|
case -EKEYREJECTED:
|
|
case -EKEYEXPIRED:
|
|
case -EKEYREVOKED:
|
|
case -EPERM:
|
|
return rxrpc_abort_conn(conn, skb, RXGK_BADKEYNO, -EKEYREJECTED,
|
|
rxgk_abort_resp_tok_nokey);
|
|
default:
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED,
|
|
rxgk_abort_resp_tok_keyerr);
|
|
}
|
|
|
|
cant_get_token:
|
|
switch (ret) {
|
|
case -ENOMEM:
|
|
goto temporary_error;
|
|
case -EINVAL:
|
|
return rxrpc_abort_conn(conn, skb, RXGK_NOTAUTH, -EKEYREJECTED,
|
|
rxgk_abort_resp_tok_internal_error);
|
|
case -ENOPKG:
|
|
return rxrpc_abort_conn(conn, skb, KRB5_PROG_KEYTYPE_NOSUPP,
|
|
-EKEYREJECTED, rxgk_abort_resp_tok_nopkg);
|
|
}
|
|
|
|
temporary_error:
|
|
/* Ignore the response packet if we got a temporary error such as
|
|
* ENOMEM. We just want to send the challenge again. Note that we
|
|
* also come out this way if the ticket decryption fails.
|
|
*/
|
|
return ret;
|
|
}
|