mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 01:49:36 +00:00
![David Howells](/assets/img/avatar_default.png)
rcu_dereference_key() and user_key_payload() are currently being used in two different, incompatible ways: (1) As a wrapper to rcu_dereference() - when only the RCU read lock used to protect the key. (2) As a wrapper to rcu_dereference_protected() - when the key semaphor is used to protect the key and the may be being modified. Fix this by splitting both of the key wrappers to produce: (1) RCU accessors for keys when caller has the key semaphore locked: dereference_key_locked() user_key_payload_locked() (2) RCU accessors for keys when caller holds the RCU read lock: dereference_key_rcu() user_key_payload_rcu() This should fix following warning in the NFS idmapper =============================== [ INFO: suspicious RCU usage. ] 4.10.0 #1 Tainted: G W ------------------------------- ./include/keys/user-type.h:53 suspicious rcu_dereference_protected() usage! other info that might help us debug this: rcu_scheduler_active = 2, debug_locks = 0 1 lock held by mount.nfs/5987: #0: (rcu_read_lock){......}, at: [<d000000002527abc>] nfs_idmap_get_key+0x15c/0x420 [nfsv4] stack backtrace: CPU: 1 PID: 5987 Comm: mount.nfs Tainted: G W 4.10.0 #1 Call Trace: dump_stack+0xe8/0x154 (unreliable) lockdep_rcu_suspicious+0x140/0x190 nfs_idmap_get_key+0x380/0x420 [nfsv4] nfs_map_name_to_uid+0x2a0/0x3b0 [nfsv4] decode_getfattr_attrs+0xfac/0x16b0 [nfsv4] decode_getfattr_generic.constprop.106+0xbc/0x150 [nfsv4] nfs4_xdr_dec_lookup_root+0xac/0xb0 [nfsv4] rpcauth_unwrap_resp+0xe8/0x140 [sunrpc] call_decode+0x29c/0x910 [sunrpc] __rpc_execute+0x140/0x8f0 [sunrpc] rpc_run_task+0x170/0x200 [sunrpc] nfs4_call_sync_sequence+0x68/0xa0 [nfsv4] _nfs4_lookup_root.isra.44+0xd0/0xf0 [nfsv4] nfs4_lookup_root+0xe0/0x350 [nfsv4] nfs4_lookup_root_sec+0x70/0xa0 [nfsv4] nfs4_find_root_sec+0xc4/0x100 [nfsv4] nfs4_proc_get_rootfh+0x5c/0xf0 [nfsv4] nfs4_get_rootfh+0x6c/0x190 [nfsv4] nfs4_server_common_setup+0xc4/0x260 [nfsv4] nfs4_create_server+0x278/0x3c0 [nfsv4] nfs4_remote_mount+0x50/0xb0 [nfsv4] mount_fs+0x74/0x210 vfs_kern_mount+0x78/0x220 nfs_do_root_mount+0xb0/0x140 [nfsv4] nfs4_try_mount+0x60/0x100 [nfsv4] nfs_fs_mount+0x5ec/0xda0 [nfs] mount_fs+0x74/0x210 vfs_kern_mount+0x78/0x220 do_mount+0x254/0xf70 SyS_mount+0x94/0x100 system_call+0x38/0xe0 Reported-by: Jan Stancek <jstancek@redhat.com> Signed-off-by: David Howells <dhowells@redhat.com> Tested-by: Jan Stancek <jstancek@redhat.com> Signed-off-by: James Morris <james.l.morris@oracle.com>
166 lines
3.3 KiB
C
166 lines
3.3 KiB
C
/* Crypto operations using stored keys
|
|
*
|
|
* Copyright (c) 2016, Intel Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/mpi.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <keys/user-type.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* Public key or shared secret generation function [RFC2631 sec 2.1.1]
|
|
*
|
|
* ya = g^xa mod p;
|
|
* or
|
|
* ZZ = yb^xa mod p;
|
|
*
|
|
* where xa is the local private key, ya is the local public key, g is
|
|
* the generator, p is the prime, yb is the remote public key, and ZZ
|
|
* is the shared secret.
|
|
*
|
|
* Both are the same calculation, so g or yb are the "base" and ya or
|
|
* ZZ are the "result".
|
|
*/
|
|
static int do_dh(MPI result, MPI base, MPI xa, MPI p)
|
|
{
|
|
return mpi_powm(result, base, xa, p);
|
|
}
|
|
|
|
static ssize_t mpi_from_key(key_serial_t keyid, size_t maxlen, MPI *mpi)
|
|
{
|
|
struct key *key;
|
|
key_ref_t key_ref;
|
|
long status;
|
|
ssize_t ret;
|
|
|
|
key_ref = lookup_user_key(keyid, 0, KEY_NEED_READ);
|
|
if (IS_ERR(key_ref)) {
|
|
ret = -ENOKEY;
|
|
goto error;
|
|
}
|
|
|
|
key = key_ref_to_ptr(key_ref);
|
|
|
|
ret = -EOPNOTSUPP;
|
|
if (key->type == &key_type_user) {
|
|
down_read(&key->sem);
|
|
status = key_validate(key);
|
|
if (status == 0) {
|
|
const struct user_key_payload *payload;
|
|
|
|
payload = user_key_payload_locked(key);
|
|
|
|
if (maxlen == 0) {
|
|
*mpi = NULL;
|
|
ret = payload->datalen;
|
|
} else if (payload->datalen <= maxlen) {
|
|
*mpi = mpi_read_raw_data(payload->data,
|
|
payload->datalen);
|
|
if (*mpi)
|
|
ret = payload->datalen;
|
|
} else {
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
up_read(&key->sem);
|
|
}
|
|
|
|
key_put(key);
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
long keyctl_dh_compute(struct keyctl_dh_params __user *params,
|
|
char __user *buffer, size_t buflen,
|
|
void __user *reserved)
|
|
{
|
|
long ret;
|
|
MPI base, private, prime, result;
|
|
unsigned nbytes;
|
|
struct keyctl_dh_params pcopy;
|
|
uint8_t *kbuf;
|
|
ssize_t keylen;
|
|
size_t resultlen;
|
|
|
|
if (!params || (!buffer && buflen)) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) {
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
if (reserved) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
keylen = mpi_from_key(pcopy.prime, buflen, &prime);
|
|
if (keylen < 0 || !prime) {
|
|
/* buflen == 0 may be used to query the required buffer size,
|
|
* which is the prime key length.
|
|
*/
|
|
ret = keylen;
|
|
goto out;
|
|
}
|
|
|
|
/* The result is never longer than the prime */
|
|
resultlen = keylen;
|
|
|
|
keylen = mpi_from_key(pcopy.base, SIZE_MAX, &base);
|
|
if (keylen < 0 || !base) {
|
|
ret = keylen;
|
|
goto error1;
|
|
}
|
|
|
|
keylen = mpi_from_key(pcopy.private, SIZE_MAX, &private);
|
|
if (keylen < 0 || !private) {
|
|
ret = keylen;
|
|
goto error2;
|
|
}
|
|
|
|
result = mpi_alloc(0);
|
|
if (!result) {
|
|
ret = -ENOMEM;
|
|
goto error3;
|
|
}
|
|
|
|
kbuf = kmalloc(resultlen, GFP_KERNEL);
|
|
if (!kbuf) {
|
|
ret = -ENOMEM;
|
|
goto error4;
|
|
}
|
|
|
|
ret = do_dh(result, base, private, prime);
|
|
if (ret)
|
|
goto error5;
|
|
|
|
ret = mpi_read_buffer(result, kbuf, resultlen, &nbytes, NULL);
|
|
if (ret != 0)
|
|
goto error5;
|
|
|
|
ret = nbytes;
|
|
if (copy_to_user(buffer, kbuf, nbytes) != 0)
|
|
ret = -EFAULT;
|
|
|
|
error5:
|
|
kfree(kbuf);
|
|
error4:
|
|
mpi_free(result);
|
|
error3:
|
|
mpi_free(private);
|
|
error2:
|
|
mpi_free(base);
|
|
error1:
|
|
mpi_free(prime);
|
|
out:
|
|
return ret;
|
|
}
|