linux/security/keys/request_key_auth.c
David Howells 04c567d931 [PATCH] Keys: Fix race between two instantiators of a key
Add a revocation notification method to the key type and calls it whilst
the key's semaphore is still write-locked after setting the revocation
flag.

The patch then uses this to maintain a reference on the task_struct of the
process that calls request_key() for as long as the authorisation key
remains unrevoked.

This fixes a potential race between two processes both of which have
assumed the authority to instantiate a key (one may have forked the other
for example).  The problem is that there's no locking around the check for
revocation of the auth key and the use of the task_struct it points to, nor
does the auth key keep a reference on the task_struct.

Access to the "context" pointer in the auth key must thenceforth be done
with the auth key semaphore held.  The revocation method is called with the
target key semaphore held write-locked and the search of the context
process's keyrings is done with the auth key semaphore read-locked.

The check for the revocation state of the auth key just prior to searching
it is done after the auth key is read-locked for the search.  This ensures
that the auth key can't be revoked between the check and the search.

The revocation notification method is added so that the context task_struct
can be released as soon as instantiation happens rather than waiting for
the auth key to be destroyed, thus avoiding the unnecessary pinning of the
requesting process.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-06-22 15:05:56 -07:00

269 lines
7.1 KiB
C

/* request_key_auth.c: request key authorisation controlling key def
*
* Copyright (C) 2005 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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.
*
* See Documentation/keys-request-key.txt
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#include "internal.h"
static int request_key_auth_instantiate(struct key *, const void *, size_t);
static void request_key_auth_describe(const struct key *, struct seq_file *);
static void request_key_auth_revoke(struct key *);
static void request_key_auth_destroy(struct key *);
static long request_key_auth_read(const struct key *, char __user *, size_t);
/*
* the request-key authorisation key type definition
*/
struct key_type key_type_request_key_auth = {
.name = ".request_key_auth",
.def_datalen = sizeof(struct request_key_auth),
.instantiate = request_key_auth_instantiate,
.describe = request_key_auth_describe,
.revoke = request_key_auth_revoke,
.destroy = request_key_auth_destroy,
.read = request_key_auth_read,
};
/*****************************************************************************/
/*
* instantiate a request-key authorisation key
*/
static int request_key_auth_instantiate(struct key *key,
const void *data,
size_t datalen)
{
key->payload.data = (struct request_key_auth *) data;
return 0;
} /* end request_key_auth_instantiate() */
/*****************************************************************************/
/*
* reading a request-key authorisation key retrieves the callout information
*/
static void request_key_auth_describe(const struct key *key,
struct seq_file *m)
{
struct request_key_auth *rka = key->payload.data;
seq_puts(m, "key:");
seq_puts(m, key->description);
seq_printf(m, " pid:%d ci:%zu", rka->pid, strlen(rka->callout_info));
} /* end request_key_auth_describe() */
/*****************************************************************************/
/*
* read the callout_info data
* - the key's semaphore is read-locked
*/
static long request_key_auth_read(const struct key *key,
char __user *buffer, size_t buflen)
{
struct request_key_auth *rka = key->payload.data;
size_t datalen;
long ret;
datalen = strlen(rka->callout_info);
ret = datalen;
/* we can return the data as is */
if (buffer && buflen > 0) {
if (buflen > datalen)
buflen = datalen;
if (copy_to_user(buffer, rka->callout_info, buflen) != 0)
ret = -EFAULT;
}
return ret;
} /* end request_key_auth_read() */
/*****************************************************************************/
/*
* handle revocation of an authorisation token key
* - called with the key sem write-locked
*/
static void request_key_auth_revoke(struct key *key)
{
struct request_key_auth *rka = key->payload.data;
kenter("{%d}", key->serial);
if (rka->context) {
put_task_struct(rka->context);
rka->context = NULL;
}
} /* end request_key_auth_revoke() */
/*****************************************************************************/
/*
* destroy an instantiation authorisation token key
*/
static void request_key_auth_destroy(struct key *key)
{
struct request_key_auth *rka = key->payload.data;
kenter("{%d}", key->serial);
if (rka->context) {
put_task_struct(rka->context);
rka->context = NULL;
}
key_put(rka->target_key);
kfree(rka);
} /* end request_key_auth_destroy() */
/*****************************************************************************/
/*
* create an authorisation token for /sbin/request-key or whoever to gain
* access to the caller's security data
*/
struct key *request_key_auth_new(struct key *target, const char *callout_info)
{
struct request_key_auth *rka, *irka;
struct key *authkey = NULL;
char desc[20];
int ret;
kenter("%d,", target->serial);
/* allocate a auth record */
rka = kmalloc(sizeof(*rka), GFP_KERNEL);
if (!rka) {
kleave(" = -ENOMEM");
return ERR_PTR(-ENOMEM);
}
/* see if the calling process is already servicing the key request of
* another process */
if (current->request_key_auth) {
/* it is - use that instantiation context here too */
down_read(&current->request_key_auth->sem);
/* if the auth key has been revoked, then the key we're
* servicing is already instantiated */
if (test_bit(KEY_FLAG_REVOKED,
&current->request_key_auth->flags))
goto auth_key_revoked;
irka = current->request_key_auth->payload.data;
rka->context = irka->context;
rka->pid = irka->pid;
get_task_struct(rka->context);
up_read(&current->request_key_auth->sem);
}
else {
/* it isn't - use this process as the context */
rka->context = current;
rka->pid = current->pid;
get_task_struct(rka->context);
}
rka->target_key = key_get(target);
rka->callout_info = callout_info;
/* allocate the auth key */
sprintf(desc, "%x", target->serial);
authkey = key_alloc(&key_type_request_key_auth, desc,
current->fsuid, current->fsgid, current,
KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
KEY_USR_VIEW, 1);
if (IS_ERR(authkey)) {
ret = PTR_ERR(authkey);
goto error_alloc;
}
/* construct and attach to the keyring */
ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL);
if (ret < 0)
goto error_inst;
kleave(" = {%d}", authkey->serial);
return authkey;
auth_key_revoked:
up_read(&current->request_key_auth->sem);
kfree(rka);
kleave("= -EKEYREVOKED");
return ERR_PTR(-EKEYREVOKED);
error_inst:
key_revoke(authkey);
key_put(authkey);
error_alloc:
key_put(rka->target_key);
kfree(rka);
kleave("= %d", ret);
return ERR_PTR(ret);
} /* end request_key_auth_new() */
/*****************************************************************************/
/*
* see if an authorisation key is associated with a particular key
*/
static int key_get_instantiation_authkey_match(const struct key *key,
const void *_id)
{
struct request_key_auth *rka = key->payload.data;
key_serial_t id = (key_serial_t)(unsigned long) _id;
return rka->target_key->serial == id;
} /* end key_get_instantiation_authkey_match() */
/*****************************************************************************/
/*
* get the authorisation key for instantiation of a specific key if attached to
* the current process's keyrings
* - this key is inserted into a keyring and that is set as /sbin/request-key's
* session keyring
* - a target_id of zero specifies any valid token
*/
struct key *key_get_instantiation_authkey(key_serial_t target_id)
{
struct key *authkey;
key_ref_t authkey_ref;
authkey_ref = search_process_keyrings(
&key_type_request_key_auth,
(void *) (unsigned long) target_id,
key_get_instantiation_authkey_match,
current);
if (IS_ERR(authkey_ref)) {
authkey = ERR_PTR(PTR_ERR(authkey_ref));
goto error;
}
authkey = key_ref_to_ptr(authkey_ref);
if (test_bit(KEY_FLAG_REVOKED, &authkey->flags)) {
key_put(authkey);
authkey = ERR_PTR(-EKEYREVOKED);
}
error:
return authkey;
} /* end key_get_instantiation_authkey() */