5a17f040fa
A common exploit pattern for ROP attacks is to abuse prepare_kernel_cred() in order to construct escalated privileges[1]. Instead of providing a short-hand argument (NULL) to the "daemon" argument to indicate using init_cred as the base cred, require that "daemon" is always set to an actual task. Replace all existing callers that were passing NULL with &init_task. Future attacks will need to have sufficiently powerful read/write primitives to have found an appropriately privileged task and written it to the ROP stack as an argument to succeed, which is similarly difficult to the prior effort needed to escalate privileges before struct cred existed: locate the current cred and overwrite the uid member. This has the added benefit of meaning that prepare_kernel_cred() can no longer exceed the privileges of the init task, which may have changed from the original init_cred (e.g. dropping capabilities from the bounding set). [1] https://google.com/search?q=commit_creds(prepare_kernel_cred(0)) Cc: "Eric W. Biederman" <ebiederm@xmission.com> Cc: David Howells <dhowells@redhat.com> Cc: "Rafael J. Wysocki" <rafael@kernel.org> Cc: Steve French <sfrench@samba.org> Cc: Ronnie Sahlberg <lsahlber@redhat.com> Cc: Shyam Prasad N <sprasad@microsoft.com> Cc: Tom Talpey <tom@talpey.com> Cc: Namjae Jeon <linkinjeon@kernel.org> Cc: Trond Myklebust <trond.myklebust@hammerspace.com> Cc: Anna Schumaker <anna@kernel.org> Cc: Chuck Lever <chuck.lever@oracle.com> Cc: Jeff Layton <jlayton@kernel.org> Cc: "David S. Miller" <davem@davemloft.net> Cc: Eric Dumazet <edumazet@google.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: "Michal Koutný" <mkoutny@suse.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Cc: linux-nfs@vger.kernel.org Cc: netdev@vger.kernel.org Signed-off-by: Kees Cook <keescook@chromium.org> Acked-by: Luis Chamberlain <mcgrof@kernel.org> Reviewed-by: Sergey Senozhatsky <senozhatsky@chromium.org> Acked-by: Russ Weight <russell.h.weight@intel.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Acked-by: Paulo Alcantara (SUSE) <pc@cjr.nz> Link: https://lore.kernel.org/r/20221026232943.never.775-kees@kernel.org
237 lines
5.7 KiB
C
237 lines
5.7 KiB
C
// SPDX-License-Identifier: LGPL-2.1
|
|
/*
|
|
* SPNEGO upcall management for CIFS
|
|
*
|
|
* Copyright (c) 2007 Red Hat, Inc.
|
|
* Author(s): Jeff Layton (jlayton@redhat.com)
|
|
*
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <keys/user-type.h>
|
|
#include <linux/key-type.h>
|
|
#include <linux/keyctl.h>
|
|
#include <linux/inet.h>
|
|
#include "cifsglob.h"
|
|
#include "cifs_spnego.h"
|
|
#include "cifs_debug.h"
|
|
#include "cifsproto.h"
|
|
static const struct cred *spnego_cred;
|
|
|
|
/* create a new cifs key */
|
|
static int
|
|
cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep)
|
|
{
|
|
char *payload;
|
|
int ret;
|
|
|
|
ret = -ENOMEM;
|
|
payload = kmemdup(prep->data, prep->datalen, GFP_KERNEL);
|
|
if (!payload)
|
|
goto error;
|
|
|
|
/* attach the data */
|
|
key->payload.data[0] = payload;
|
|
ret = 0;
|
|
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
cifs_spnego_key_destroy(struct key *key)
|
|
{
|
|
kfree(key->payload.data[0]);
|
|
}
|
|
|
|
|
|
/*
|
|
* keytype for CIFS spnego keys
|
|
*/
|
|
struct key_type cifs_spnego_key_type = {
|
|
.name = "cifs.spnego",
|
|
.instantiate = cifs_spnego_key_instantiate,
|
|
.destroy = cifs_spnego_key_destroy,
|
|
.describe = user_describe,
|
|
};
|
|
|
|
/* length of longest version string e.g. strlen("ver=0xFF") */
|
|
#define MAX_VER_STR_LEN 8
|
|
|
|
/* length of longest security mechanism name, eg in future could have
|
|
* strlen(";sec=ntlmsspi") */
|
|
#define MAX_MECH_STR_LEN 13
|
|
|
|
/* strlen of "host=" */
|
|
#define HOST_KEY_LEN 5
|
|
|
|
/* strlen of ";ip4=" or ";ip6=" */
|
|
#define IP_KEY_LEN 5
|
|
|
|
/* strlen of ";uid=0x" */
|
|
#define UID_KEY_LEN 7
|
|
|
|
/* strlen of ";creduid=0x" */
|
|
#define CREDUID_KEY_LEN 11
|
|
|
|
/* strlen of ";user=" */
|
|
#define USER_KEY_LEN 6
|
|
|
|
/* strlen of ";pid=0x" */
|
|
#define PID_KEY_LEN 7
|
|
|
|
/* get a key struct with a SPNEGO security blob, suitable for session setup */
|
|
struct key *
|
|
cifs_get_spnego_key(struct cifs_ses *sesInfo,
|
|
struct TCP_Server_Info *server)
|
|
{
|
|
struct sockaddr_in *sa = (struct sockaddr_in *) &server->dstaddr;
|
|
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) &server->dstaddr;
|
|
char *description, *dp;
|
|
size_t desc_len;
|
|
struct key *spnego_key;
|
|
const char *hostname = server->hostname;
|
|
const struct cred *saved_cred;
|
|
|
|
/* length of fields (with semicolons): ver=0xyz ip4=ipaddress
|
|
host=hostname sec=mechanism uid=0xFF user=username */
|
|
desc_len = MAX_VER_STR_LEN +
|
|
HOST_KEY_LEN + strlen(hostname) +
|
|
IP_KEY_LEN + INET6_ADDRSTRLEN +
|
|
MAX_MECH_STR_LEN +
|
|
UID_KEY_LEN + (sizeof(uid_t) * 2) +
|
|
CREDUID_KEY_LEN + (sizeof(uid_t) * 2) +
|
|
PID_KEY_LEN + (sizeof(pid_t) * 2) + 1;
|
|
|
|
if (sesInfo->user_name)
|
|
desc_len += USER_KEY_LEN + strlen(sesInfo->user_name);
|
|
|
|
spnego_key = ERR_PTR(-ENOMEM);
|
|
description = kzalloc(desc_len, GFP_KERNEL);
|
|
if (description == NULL)
|
|
goto out;
|
|
|
|
dp = description;
|
|
/* start with version and hostname portion of UNC string */
|
|
spnego_key = ERR_PTR(-EINVAL);
|
|
sprintf(dp, "ver=0x%x;host=%s;", CIFS_SPNEGO_UPCALL_VERSION,
|
|
hostname);
|
|
dp = description + strlen(description);
|
|
|
|
/* add the server address */
|
|
if (server->dstaddr.ss_family == AF_INET)
|
|
sprintf(dp, "ip4=%pI4", &sa->sin_addr);
|
|
else if (server->dstaddr.ss_family == AF_INET6)
|
|
sprintf(dp, "ip6=%pI6", &sa6->sin6_addr);
|
|
else
|
|
goto out;
|
|
|
|
dp = description + strlen(description);
|
|
|
|
/* for now, only sec=krb5 and sec=mskrb5 are valid */
|
|
if (server->sec_kerberos)
|
|
sprintf(dp, ";sec=krb5");
|
|
else if (server->sec_mskerberos)
|
|
sprintf(dp, ";sec=mskrb5");
|
|
else {
|
|
cifs_dbg(VFS, "unknown or missing server auth type, use krb5\n");
|
|
sprintf(dp, ";sec=krb5");
|
|
}
|
|
|
|
dp = description + strlen(description);
|
|
sprintf(dp, ";uid=0x%x",
|
|
from_kuid_munged(&init_user_ns, sesInfo->linux_uid));
|
|
|
|
dp = description + strlen(description);
|
|
sprintf(dp, ";creduid=0x%x",
|
|
from_kuid_munged(&init_user_ns, sesInfo->cred_uid));
|
|
|
|
if (sesInfo->user_name) {
|
|
dp = description + strlen(description);
|
|
sprintf(dp, ";user=%s", sesInfo->user_name);
|
|
}
|
|
|
|
dp = description + strlen(description);
|
|
sprintf(dp, ";pid=0x%x", current->pid);
|
|
|
|
cifs_dbg(FYI, "key description = %s\n", description);
|
|
saved_cred = override_creds(spnego_cred);
|
|
spnego_key = request_key(&cifs_spnego_key_type, description, "");
|
|
revert_creds(saved_cred);
|
|
|
|
#ifdef CONFIG_CIFS_DEBUG2
|
|
if (cifsFYI && !IS_ERR(spnego_key)) {
|
|
struct cifs_spnego_msg *msg = spnego_key->payload.data[0];
|
|
cifs_dump_mem("SPNEGO reply blob:", msg->data, min(1024U,
|
|
msg->secblob_len + msg->sesskey_len));
|
|
}
|
|
#endif /* CONFIG_CIFS_DEBUG2 */
|
|
|
|
out:
|
|
kfree(description);
|
|
return spnego_key;
|
|
}
|
|
|
|
int
|
|
init_cifs_spnego(void)
|
|
{
|
|
struct cred *cred;
|
|
struct key *keyring;
|
|
int ret;
|
|
|
|
cifs_dbg(FYI, "Registering the %s key type\n",
|
|
cifs_spnego_key_type.name);
|
|
|
|
/*
|
|
* Create an override credential set with special thread keyring for
|
|
* spnego upcalls.
|
|
*/
|
|
|
|
cred = prepare_kernel_cred(&init_task);
|
|
if (!cred)
|
|
return -ENOMEM;
|
|
|
|
keyring = keyring_alloc(".cifs_spnego",
|
|
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
|
|
(KEY_POS_ALL & ~KEY_POS_SETATTR) |
|
|
KEY_USR_VIEW | KEY_USR_READ,
|
|
KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
|
|
if (IS_ERR(keyring)) {
|
|
ret = PTR_ERR(keyring);
|
|
goto failed_put_cred;
|
|
}
|
|
|
|
ret = register_key_type(&cifs_spnego_key_type);
|
|
if (ret < 0)
|
|
goto failed_put_key;
|
|
|
|
/*
|
|
* instruct request_key() to use this special keyring as a cache for
|
|
* the results it looks up
|
|
*/
|
|
set_bit(KEY_FLAG_ROOT_CAN_CLEAR, &keyring->flags);
|
|
cred->thread_keyring = keyring;
|
|
cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
|
|
spnego_cred = cred;
|
|
|
|
cifs_dbg(FYI, "cifs spnego keyring: %d\n", key_serial(keyring));
|
|
return 0;
|
|
|
|
failed_put_key:
|
|
key_put(keyring);
|
|
failed_put_cred:
|
|
put_cred(cred);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
exit_cifs_spnego(void)
|
|
{
|
|
key_revoke(spnego_cred->thread_keyring);
|
|
unregister_key_type(&cifs_spnego_key_type);
|
|
put_cred(spnego_cred);
|
|
cifs_dbg(FYI, "Unregistered %s key type\n", cifs_spnego_key_type.name);
|
|
}
|