linux-stable/security/keys/keyctl.c
David Howells 76d8aeabfe [PATCH] keys: Discard key spinlock and use RCU for key payload
The attached patch changes the key implementation in a number of ways:

 (1) It removes the spinlock from the key structure.

 (2) The key flags are now accessed using atomic bitops instead of
     write-locking the key spinlock and using C bitwise operators.

     The three instantiation flags are dealt with with the construction
     semaphore held during the request_key/instantiate/negate sequence, thus
     rendering the spinlock superfluous.

     The key flags are also now bit numbers not bit masks.

 (3) The key payload is now accessed using RCU. This permits the recursive
     keyring search algorithm to be simplified greatly since no locks need be
     taken other than the usual RCU preemption disablement. Searching now does
     not require any locks or semaphores to be held; merely that the starting
     keyring be pinned.

 (4) The keyring payload now includes an RCU head so that it can be disposed
     of by call_rcu(). This requires that the payload be copied on unlink to
     prevent introducing races in copy-down vs search-up.

 (5) The user key payload is now a structure with the data following it. It
     includes an RCU head like the keyring payload and for the same reason. It
     also contains a data length because the data length in the key may be
     changed on another CPU whilst an RCU protected read is in progress on the
     payload. This would then see the supposed RCU payload and the on-key data
     length getting out of sync.

     I'm tempted to drop the key's datalen entirely, except that it's used in
     conjunction with quota management and so is a little tricky to get rid
     of.

 (6) Update the keys documentation.

Signed-Off-By: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-06-24 00:05:18 -07:00

978 lines
22 KiB
C

/* keyctl.c: userspace keyctl operations
*
* Copyright (C) 2004 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/keyctl.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <asm/uaccess.h>
#include "internal.h"
/*****************************************************************************/
/*
* extract the description of a new key from userspace and either add it as a
* new key to the specified keyring or update a matching key in that keyring
* - the keyring must be writable
* - returns the new key's serial number
* - implements add_key()
*/
asmlinkage long sys_add_key(const char __user *_type,
const char __user *_description,
const void __user *_payload,
size_t plen,
key_serial_t ringid)
{
struct key *keyring, *key;
char type[32], *description;
void *payload;
long dlen, ret;
ret = -EINVAL;
if (plen > 32767)
goto error;
/* draw all the data into kernel space */
ret = strncpy_from_user(type, _type, sizeof(type) - 1);
if (ret < 0)
goto error;
type[31] = '\0';
ret = -EFAULT;
dlen = strnlen_user(_description, PAGE_SIZE - 1);
if (dlen <= 0)
goto error;
ret = -EINVAL;
if (dlen > PAGE_SIZE - 1)
goto error;
ret = -ENOMEM;
description = kmalloc(dlen + 1, GFP_KERNEL);
if (!description)
goto error;
ret = -EFAULT;
if (copy_from_user(description, _description, dlen + 1) != 0)
goto error2;
/* pull the payload in if one was supplied */
payload = NULL;
if (_payload) {
ret = -ENOMEM;
payload = kmalloc(plen, GFP_KERNEL);
if (!payload)
goto error2;
ret = -EFAULT;
if (copy_from_user(payload, _payload, plen) != 0)
goto error3;
}
/* find the target keyring (which must be writable) */
keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error3;
}
/* create or update the requested key and add it to the target
* keyring */
key = key_create_or_update(keyring, type, description,
payload, plen, 0);
if (!IS_ERR(key)) {
ret = key->serial;
key_put(key);
}
else {
ret = PTR_ERR(key);
}
key_put(keyring);
error3:
kfree(payload);
error2:
kfree(description);
error:
return ret;
} /* end sys_add_key() */
/*****************************************************************************/
/*
* search the process keyrings for a matching key
* - nested keyrings may also be searched if they have Search permission
* - if a key is found, it will be attached to the destination keyring if
* there's one specified
* - /sbin/request-key will be invoked if _callout_info is non-NULL
* - the _callout_info string will be passed to /sbin/request-key
* - if the _callout_info string is empty, it will be rendered as "-"
* - implements request_key()
*/
asmlinkage long sys_request_key(const char __user *_type,
const char __user *_description,
const char __user *_callout_info,
key_serial_t destringid)
{
struct key_type *ktype;
struct key *key, *dest;
char type[32], *description, *callout_info;
long dlen, ret;
/* pull the type into kernel space */
ret = strncpy_from_user(type, _type, sizeof(type) - 1);
if (ret < 0)
goto error;
type[31] = '\0';
/* pull the description into kernel space */
ret = -EFAULT;
dlen = strnlen_user(_description, PAGE_SIZE - 1);
if (dlen <= 0)
goto error;
ret = -EINVAL;
if (dlen > PAGE_SIZE - 1)
goto error;
ret = -ENOMEM;
description = kmalloc(dlen + 1, GFP_KERNEL);
if (!description)
goto error;
ret = -EFAULT;
if (copy_from_user(description, _description, dlen + 1) != 0)
goto error2;
/* pull the callout info into kernel space */
callout_info = NULL;
if (_callout_info) {
ret = -EFAULT;
dlen = strnlen_user(_callout_info, PAGE_SIZE - 1);
if (dlen <= 0)
goto error2;
ret = -EINVAL;
if (dlen > PAGE_SIZE - 1)
goto error2;
ret = -ENOMEM;
callout_info = kmalloc(dlen + 1, GFP_KERNEL);
if (!callout_info)
goto error2;
ret = -EFAULT;
if (copy_from_user(callout_info, _callout_info, dlen + 1) != 0)
goto error3;
}
/* get the destination keyring if specified */
dest = NULL;
if (destringid) {
dest = lookup_user_key(destringid, 1, 0, KEY_WRITE);
if (IS_ERR(dest)) {
ret = PTR_ERR(dest);
goto error3;
}
}
/* find the key type */
ktype = key_type_lookup(type);
if (IS_ERR(ktype)) {
ret = PTR_ERR(ktype);
goto error4;
}
/* do the search */
key = request_key(ktype, description, callout_info);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error5;
}
/* link the resulting key to the destination keyring */
if (dest) {
ret = key_link(dest, key);
if (ret < 0)
goto error6;
}
ret = key->serial;
error6:
key_put(key);
error5:
key_type_put(ktype);
error4:
key_put(dest);
error3:
kfree(callout_info);
error2:
kfree(description);
error:
return ret;
} /* end sys_request_key() */
/*****************************************************************************/
/*
* get the ID of the specified process keyring
* - the keyring must have search permission to be found
* - implements keyctl(KEYCTL_GET_KEYRING_ID)
*/
long keyctl_get_keyring_ID(key_serial_t id, int create)
{
struct key *key;
long ret;
key = lookup_user_key(id, create, 0, KEY_SEARCH);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
ret = key->serial;
key_put(key);
error:
return ret;
} /* end keyctl_get_keyring_ID() */
/*****************************************************************************/
/*
* join the session keyring
* - implements keyctl(KEYCTL_JOIN_SESSION_KEYRING)
*/
long keyctl_join_session_keyring(const char __user *_name)
{
char *name;
long nlen, ret;
/* fetch the name from userspace */
name = NULL;
if (_name) {
ret = -EFAULT;
nlen = strnlen_user(_name, PAGE_SIZE - 1);
if (nlen <= 0)
goto error;
ret = -EINVAL;
if (nlen > PAGE_SIZE - 1)
goto error;
ret = -ENOMEM;
name = kmalloc(nlen + 1, GFP_KERNEL);
if (!name)
goto error;
ret = -EFAULT;
if (copy_from_user(name, _name, nlen + 1) != 0)
goto error2;
}
/* join the session */
ret = join_session_keyring(name);
error2:
kfree(name);
error:
return ret;
} /* end keyctl_join_session_keyring() */
/*****************************************************************************/
/*
* update a key's data payload
* - the key must be writable
* - implements keyctl(KEYCTL_UPDATE)
*/
long keyctl_update_key(key_serial_t id,
const void __user *_payload,
size_t plen)
{
struct key *key;
void *payload;
long ret;
ret = -EINVAL;
if (plen > PAGE_SIZE)
goto error;
/* pull the payload in if one was supplied */
payload = NULL;
if (_payload) {
ret = -ENOMEM;
payload = kmalloc(plen, GFP_KERNEL);
if (!payload)
goto error;
ret = -EFAULT;
if (copy_from_user(payload, _payload, plen) != 0)
goto error2;
}
/* find the target key (which must be writable) */
key = lookup_user_key(id, 0, 0, KEY_WRITE);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error2;
}
/* update the key */
ret = key_update(key, payload, plen);
key_put(key);
error2:
kfree(payload);
error:
return ret;
} /* end keyctl_update_key() */
/*****************************************************************************/
/*
* revoke a key
* - the key must be writable
* - implements keyctl(KEYCTL_REVOKE)
*/
long keyctl_revoke_key(key_serial_t id)
{
struct key *key;
long ret;
key = lookup_user_key(id, 0, 0, KEY_WRITE);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
key_revoke(key);
ret = 0;
key_put(key);
error:
return 0;
} /* end keyctl_revoke_key() */
/*****************************************************************************/
/*
* clear the specified process keyring
* - the keyring must be writable
* - implements keyctl(KEYCTL_CLEAR)
*/
long keyctl_keyring_clear(key_serial_t ringid)
{
struct key *keyring;
long ret;
keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error;
}
ret = keyring_clear(keyring);
key_put(keyring);
error:
return ret;
} /* end keyctl_keyring_clear() */
/*****************************************************************************/
/*
* link a key into a keyring
* - the keyring must be writable
* - the key must be linkable
* - implements keyctl(KEYCTL_LINK)
*/
long keyctl_keyring_link(key_serial_t id, key_serial_t ringid)
{
struct key *keyring, *key;
long ret;
keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error;
}
key = lookup_user_key(id, 1, 0, KEY_LINK);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error2;
}
ret = key_link(keyring, key);
key_put(key);
error2:
key_put(keyring);
error:
return ret;
} /* end keyctl_keyring_link() */
/*****************************************************************************/
/*
* unlink the first attachment of a key from a keyring
* - the keyring must be writable
* - we don't need any permissions on the key
* - implements keyctl(KEYCTL_UNLINK)
*/
long keyctl_keyring_unlink(key_serial_t id, key_serial_t ringid)
{
struct key *keyring, *key;
long ret;
keyring = lookup_user_key(ringid, 0, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error;
}
key = lookup_user_key(id, 0, 0, 0);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error2;
}
ret = key_unlink(keyring, key);
key_put(key);
error2:
key_put(keyring);
error:
return ret;
} /* end keyctl_keyring_unlink() */
/*****************************************************************************/
/*
* describe a user key
* - the key must have view permission
* - if there's a buffer, we place up to buflen bytes of data into it
* - unless there's an error, we return the amount of description available,
* irrespective of how much we may have copied
* - the description is formatted thus:
* type;uid;gid;perm;description<NUL>
* - implements keyctl(KEYCTL_DESCRIBE)
*/
long keyctl_describe_key(key_serial_t keyid,
char __user *buffer,
size_t buflen)
{
struct key *key;
char *tmpbuf;
long ret;
key = lookup_user_key(keyid, 0, 1, KEY_VIEW);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
/* calculate how much description we're going to return */
ret = -ENOMEM;
tmpbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmpbuf)
goto error2;
ret = snprintf(tmpbuf, PAGE_SIZE - 1,
"%s;%d;%d;%06x;%s",
key->type->name,
key->uid,
key->gid,
key->perm,
key->description ? key->description :""
);
/* include a NUL char at the end of the data */
if (ret > PAGE_SIZE - 1)
ret = PAGE_SIZE - 1;
tmpbuf[ret] = 0;
ret++;
/* consider returning the data */
if (buffer && buflen > 0) {
if (buflen > ret)
buflen = ret;
if (copy_to_user(buffer, tmpbuf, buflen) != 0)
ret = -EFAULT;
}
kfree(tmpbuf);
error2:
key_put(key);
error:
return ret;
} /* end keyctl_describe_key() */
/*****************************************************************************/
/*
* search the specified keyring for a matching key
* - the start keyring must be searchable
* - nested keyrings may also be searched if they are searchable
* - only keys with search permission may be found
* - if a key is found, it will be attached to the destination keyring if
* there's one specified
* - implements keyctl(KEYCTL_SEARCH)
*/
long keyctl_keyring_search(key_serial_t ringid,
const char __user *_type,
const char __user *_description,
key_serial_t destringid)
{
struct key_type *ktype;
struct key *keyring, *key, *dest;
char type[32], *description;
long dlen, ret;
/* pull the type and description into kernel space */
ret = strncpy_from_user(type, _type, sizeof(type) - 1);
if (ret < 0)
goto error;
type[31] = '\0';
ret = -EFAULT;
dlen = strnlen_user(_description, PAGE_SIZE - 1);
if (dlen <= 0)
goto error;
ret = -EINVAL;
if (dlen > PAGE_SIZE - 1)
goto error;
ret = -ENOMEM;
description = kmalloc(dlen + 1, GFP_KERNEL);
if (!description)
goto error;
ret = -EFAULT;
if (copy_from_user(description, _description, dlen + 1) != 0)
goto error2;
/* get the keyring at which to begin the search */
keyring = lookup_user_key(ringid, 0, 0, KEY_SEARCH);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
/* get the destination keyring if specified */
dest = NULL;
if (destringid) {
dest = lookup_user_key(destringid, 1, 0, KEY_WRITE);
if (IS_ERR(dest)) {
ret = PTR_ERR(dest);
goto error3;
}
}
/* find the key type */
ktype = key_type_lookup(type);
if (IS_ERR(ktype)) {
ret = PTR_ERR(ktype);
goto error4;
}
/* do the search */
key = keyring_search(keyring, ktype, description);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
/* treat lack or presence of a negative key the same */
if (ret == -EAGAIN)
ret = -ENOKEY;
goto error5;
}
/* link the resulting key to the destination keyring if we can */
if (dest) {
ret = -EACCES;
if (!key_permission(key, KEY_LINK))
goto error6;
ret = key_link(dest, key);
if (ret < 0)
goto error6;
}
ret = key->serial;
error6:
key_put(key);
error5:
key_type_put(ktype);
error4:
key_put(dest);
error3:
key_put(keyring);
error2:
kfree(description);
error:
return ret;
} /* end keyctl_keyring_search() */
/*****************************************************************************/
/*
* see if the key we're looking at is the target key
*/
static int keyctl_read_key_same(const struct key *key, const void *target)
{
return key == target;
} /* end keyctl_read_key_same() */
/*****************************************************************************/
/*
* read a user key's payload
* - the keyring must be readable or the key must be searchable from the
* process's keyrings
* - if there's a buffer, we place up to buflen bytes of data into it
* - unless there's an error, we return the amount of data in the key,
* irrespective of how much we may have copied
* - implements keyctl(KEYCTL_READ)
*/
long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
{
struct key *key, *skey;
long ret;
/* find the key first */
key = lookup_user_key(keyid, 0, 0, 0);
if (!IS_ERR(key)) {
/* see if we can read it directly */
if (key_permission(key, KEY_READ))
goto can_read_key;
/* can't; see if it's searchable from this process's
* keyrings */
ret = -ENOKEY;
if (key_permission(key, KEY_SEARCH)) {
/* okay - we do have search permission on the key
* itself, but do we have the key? */
skey = search_process_keyrings_aux(key->type, key,
keyctl_read_key_same);
if (!IS_ERR(skey))
goto can_read_key2;
}
goto error2;
}
ret = -ENOKEY;
goto error;
/* the key is probably readable - now try to read it */
can_read_key2:
key_put(skey);
can_read_key:
ret = key_validate(key);
if (ret == 0) {
ret = -EOPNOTSUPP;
if (key->type->read) {
/* read the data with the semaphore held (since we
* might sleep) */
down_read(&key->sem);
ret = key->type->read(key, buffer, buflen);
up_read(&key->sem);
}
}
error2:
key_put(key);
error:
return ret;
} /* end keyctl_read_key() */
/*****************************************************************************/
/*
* change the ownership of a key
* - the keyring owned by the changer
* - if the uid or gid is -1, then that parameter is not changed
* - implements keyctl(KEYCTL_CHOWN)
*/
long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
{
struct key *key;
long ret;
ret = 0;
if (uid == (uid_t) -1 && gid == (gid_t) -1)
goto error;
key = lookup_user_key(id, 1, 1, 0);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
/* make the changes with the locks held to prevent chown/chown races */
ret = -EACCES;
down_write(&key->sem);
if (!capable(CAP_SYS_ADMIN)) {
/* only the sysadmin can chown a key to some other UID */
if (uid != (uid_t) -1 && key->uid != uid)
goto no_access;
/* only the sysadmin can set the key's GID to a group other
* than one of those that the current process subscribes to */
if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid))
goto no_access;
}
/* change the UID (have to update the quotas) */
if (uid != (uid_t) -1 && uid != key->uid) {
/* don't support UID changing yet */
ret = -EOPNOTSUPP;
goto no_access;
}
/* change the GID */
if (gid != (gid_t) -1)
key->gid = gid;
ret = 0;
no_access:
up_write(&key->sem);
key_put(key);
error:
return ret;
} /* end keyctl_chown_key() */
/*****************************************************************************/
/*
* change the permission mask on a key
* - the keyring owned by the changer
* - implements keyctl(KEYCTL_SETPERM)
*/
long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
{
struct key *key;
long ret;
ret = -EINVAL;
if (perm & ~(KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL))
goto error;
key = lookup_user_key(id, 1, 1, 0);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
/* make the changes with the locks held to prevent chown/chmod races */
ret = -EACCES;
down_write(&key->sem);
/* if we're not the sysadmin, we can only change a key that we own */
if (capable(CAP_SYS_ADMIN) || key->uid == current->fsuid) {
key->perm = perm;
ret = 0;
}
up_write(&key->sem);
key_put(key);
error:
return ret;
} /* end keyctl_setperm_key() */
/*****************************************************************************/
/*
* instantiate the key with the specified payload, and, if one is given, link
* the key into the keyring
*/
long keyctl_instantiate_key(key_serial_t id,
const void __user *_payload,
size_t plen,
key_serial_t ringid)
{
struct key *key, *keyring;
void *payload;
long ret;
ret = -EINVAL;
if (plen > 32767)
goto error;
/* pull the payload in if one was supplied */
payload = NULL;
if (_payload) {
ret = -ENOMEM;
payload = kmalloc(plen, GFP_KERNEL);
if (!payload)
goto error;
ret = -EFAULT;
if (copy_from_user(payload, _payload, plen) != 0)
goto error2;
}
/* find the target key (which must be writable) */
key = lookup_user_key(id, 0, 1, KEY_WRITE);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error2;
}
/* find the destination keyring if present (which must also be
* writable) */
keyring = NULL;
if (ringid) {
keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error3;
}
}
/* instantiate the key and link it into a keyring */
ret = key_instantiate_and_link(key, payload, plen, keyring);
key_put(keyring);
error3:
key_put(key);
error2:
kfree(payload);
error:
return ret;
} /* end keyctl_instantiate_key() */
/*****************************************************************************/
/*
* negatively instantiate the key with the given timeout (in seconds), and, if
* one is given, link the key into the keyring
*/
long keyctl_negate_key(key_serial_t id, unsigned timeout, key_serial_t ringid)
{
struct key *key, *keyring;
long ret;
/* find the target key (which must be writable) */
key = lookup_user_key(id, 0, 1, KEY_WRITE);
if (IS_ERR(key)) {
ret = PTR_ERR(key);
goto error;
}
/* find the destination keyring if present (which must also be
* writable) */
keyring = NULL;
if (ringid) {
keyring = lookup_user_key(ringid, 1, 0, KEY_WRITE);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
}
/* instantiate the key and link it into a keyring */
ret = key_negate_and_link(key, timeout, keyring);
key_put(keyring);
error2:
key_put(key);
error:
return ret;
} /* end keyctl_negate_key() */
/*****************************************************************************/
/*
* the key control system call
*/
asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
switch (option) {
case KEYCTL_GET_KEYRING_ID:
return keyctl_get_keyring_ID((key_serial_t) arg2,
(int) arg3);
case KEYCTL_JOIN_SESSION_KEYRING:
return keyctl_join_session_keyring((const char __user *) arg2);
case KEYCTL_UPDATE:
return keyctl_update_key((key_serial_t) arg2,
(const void __user *) arg3,
(size_t) arg4);
case KEYCTL_REVOKE:
return keyctl_revoke_key((key_serial_t) arg2);
case KEYCTL_DESCRIBE:
return keyctl_describe_key((key_serial_t) arg2,
(char __user *) arg3,
(unsigned) arg4);
case KEYCTL_CLEAR:
return keyctl_keyring_clear((key_serial_t) arg2);
case KEYCTL_LINK:
return keyctl_keyring_link((key_serial_t) arg2,
(key_serial_t) arg3);
case KEYCTL_UNLINK:
return keyctl_keyring_unlink((key_serial_t) arg2,
(key_serial_t) arg3);
case KEYCTL_SEARCH:
return keyctl_keyring_search((key_serial_t) arg2,
(const char __user *) arg3,
(const char __user *) arg4,
(key_serial_t) arg5);
case KEYCTL_READ:
return keyctl_read_key((key_serial_t) arg2,
(char __user *) arg3,
(size_t) arg4);
case KEYCTL_CHOWN:
return keyctl_chown_key((key_serial_t) arg2,
(uid_t) arg3,
(gid_t) arg4);
case KEYCTL_SETPERM:
return keyctl_setperm_key((key_serial_t) arg2,
(key_perm_t) arg3);
case KEYCTL_INSTANTIATE:
return keyctl_instantiate_key((key_serial_t) arg2,
(const void __user *) arg3,
(size_t) arg4,
(key_serial_t) arg5);
case KEYCTL_NEGATE:
return keyctl_negate_key((key_serial_t) arg2,
(unsigned) arg3,
(key_serial_t) arg4);
default:
return -EOPNOTSUPP;
}
} /* end sys_keyctl() */