[S390] hvc_iucv: Provide IUCV z/VM user ID filtering

This patch introduces the kernel parameter hvc_iucv_allow= that specifies
a comma-separated list of z/VM user IDs.
If specified, the z/VM IUCV hypervisor console device driver accepts IUCV
connections from listed z/VM user IDs only.

Signed-off-by: Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
Hendrik Brueckner 2009-03-26 15:23:55 +01:00 committed by Martin Schwidefsky
parent 82f3a79bc6
commit 431429ff78
2 changed files with 249 additions and 8 deletions

View file

@ -829,6 +829,9 @@ and is between 256 and 4096 characters. It is defined in the file
hvc_iucv= [S390] Number of z/VM IUCV hypervisor console (HVC)
terminal devices. Valid values: 0..8
hvc_iucv_allow= [S390] Comma-separated list of z/VM user IDs.
If specified, z/VM IUCV HVC accepts connections
from listed z/VM user IDs only.
i8042.debug [HW] Toggle i8042 debug mode
i8042.direct [HW] Put keyboard port into non-translated mode

View file

@ -13,10 +13,11 @@
#include <linux/types.h>
#include <asm/ebcdic.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/tty.h>
#include <linux/wait.h>
#include <net/iucv/iucv.h>
@ -95,6 +96,12 @@ static unsigned long hvc_iucv_devices = 1;
/* Array of allocated hvc iucv tty lines... */
static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES];
#define IUCV_HVC_CON_IDX (0)
/* List of z/VM user ID filter entries (struct iucv_vmid_filter) */
#define MAX_VMID_FILTER (500)
static size_t hvc_iucv_filter_size;
static void *hvc_iucv_filter;
static const char *hvc_iucv_filter_string;
static DEFINE_RWLOCK(hvc_iucv_filter_lock);
/* Kmem cache and mempool for iucv_tty_buffer elements */
static struct kmem_cache *hvc_iucv_buffer_cache;
@ -617,6 +624,27 @@ static void hvc_iucv_notifier_del(struct hvc_struct *hp, int id)
}
}
/**
* hvc_iucv_filter_connreq() - Filter connection request based on z/VM user ID
* @ipvmid: Originating z/VM user ID (right padded with blanks)
*
* Returns 0 if the z/VM user ID @ipvmid is allowed to connection, otherwise
* non-zero.
*/
static int hvc_iucv_filter_connreq(u8 ipvmid[8])
{
size_t i;
/* Note: default policy is ACCEPT if no filter is set */
if (!hvc_iucv_filter_size)
return 0;
for (i = 0; i < hvc_iucv_filter_size; i++)
if (0 == memcmp(ipvmid, hvc_iucv_filter + (8 * i), 8))
return 0;
return 1;
}
/**
* hvc_iucv_path_pending() - IUCV handler to process a connection request.
* @path: Pending path (struct iucv_path)
@ -641,6 +669,7 @@ static int hvc_iucv_path_pending(struct iucv_path *path,
{
struct hvc_iucv_private *priv;
u8 nuser_data[16];
u8 vm_user_id[9];
int i, rc;
priv = NULL;
@ -653,6 +682,20 @@ static int hvc_iucv_path_pending(struct iucv_path *path,
if (!priv)
return -ENODEV;
/* Enforce that ipvmid is allowed to connect to us */
read_lock(&hvc_iucv_filter_lock);
rc = hvc_iucv_filter_connreq(ipvmid);
read_unlock(&hvc_iucv_filter_lock);
if (rc) {
iucv_path_sever(path, ipuser);
iucv_path_free(path);
memcpy(vm_user_id, ipvmid, 8);
vm_user_id[8] = 0;
pr_info("A connection request from z/VM user ID %s "
"was refused\n", vm_user_id);
return 0;
}
spin_lock(&priv->lock);
/* If the terminal is already connected or being severed, then sever
@ -876,6 +919,171 @@ static int __init hvc_iucv_alloc(int id, unsigned int is_console)
return 0;
}
/**
* hvc_iucv_parse_filter() - Parse filter for a single z/VM user ID
* @filter: String containing a comma-separated list of z/VM user IDs
*/
static const char *hvc_iucv_parse_filter(const char *filter, char *dest)
{
const char *nextdelim, *residual;
size_t len;
nextdelim = strchr(filter, ',');
if (nextdelim) {
len = nextdelim - filter;
residual = nextdelim + 1;
} else {
len = strlen(filter);
residual = filter + len;
}
if (len == 0)
return ERR_PTR(-EINVAL);
/* check for '\n' (if called from sysfs) */
if (filter[len - 1] == '\n')
len--;
if (len > 8)
return ERR_PTR(-EINVAL);
/* pad with blanks and save upper case version of user ID */
memset(dest, ' ', 8);
while (len--)
dest[len] = toupper(filter[len]);
return residual;
}
/**
* hvc_iucv_setup_filter() - Set up z/VM user ID filter
* @filter: String consisting of a comma-separated list of z/VM user IDs
*
* The function parses the @filter string and creates an array containing
* the list of z/VM user ID filter entries.
* Return code 0 means success, -EINVAL if the filter is syntactically
* incorrect, -ENOMEM if there was not enough memory to allocate the
* filter list array, or -ENOSPC if too many z/VM user IDs have been specified.
*/
static int hvc_iucv_setup_filter(const char *val)
{
const char *residual;
int err;
size_t size, count;
void *array, *old_filter;
count = strlen(val);
if (count == 0 || (count == 1 && val[0] == '\n')) {
size = 0;
array = NULL;
goto out_replace_filter; /* clear filter */
}
/* count user IDs in order to allocate sufficient memory */
size = 1;
residual = val;
while ((residual = strchr(residual, ',')) != NULL) {
residual++;
size++;
}
/* check if the specified list exceeds the filter limit */
if (size > MAX_VMID_FILTER)
return -ENOSPC;
array = kzalloc(size * 8, GFP_KERNEL);
if (!array)
return -ENOMEM;
count = size;
residual = val;
while (*residual && count) {
residual = hvc_iucv_parse_filter(residual,
array + ((size - count) * 8));
if (IS_ERR(residual)) {
err = PTR_ERR(residual);
kfree(array);
goto out_err;
}
count--;
}
out_replace_filter:
write_lock_bh(&hvc_iucv_filter_lock);
old_filter = hvc_iucv_filter;
hvc_iucv_filter_size = size;
hvc_iucv_filter = array;
write_unlock_bh(&hvc_iucv_filter_lock);
kfree(old_filter);
err = 0;
out_err:
return err;
}
/**
* param_set_vmidfilter() - Set z/VM user ID filter parameter
* @val: String consisting of a comma-separated list of z/VM user IDs
* @kp: Kernel parameter pointing to hvc_iucv_filter array
*
* The function sets up the z/VM user ID filter specified as comma-separated
* list of user IDs in @val.
* Note: If it is called early in the boot process, @val is stored and
* parsed later in hvc_iucv_init().
*/
static int param_set_vmidfilter(const char *val, struct kernel_param *kp)
{
int rc;
if (!MACHINE_IS_VM || !hvc_iucv_devices)
return -ENODEV;
if (!val)
return -EINVAL;
rc = 0;
if (slab_is_available())
rc = hvc_iucv_setup_filter(val);
else
hvc_iucv_filter_string = val; /* defer... */
return rc;
}
/**
* param_get_vmidfilter() - Get z/VM user ID filter
* @buffer: Buffer to store z/VM user ID filter,
* (buffer size assumption PAGE_SIZE)
* @kp: Kernel parameter pointing to the hvc_iucv_filter array
*
* The function stores the filter as a comma-separated list of z/VM user IDs
* in @buffer. Typically, sysfs routines call this function for attr show.
*/
static int param_get_vmidfilter(char *buffer, struct kernel_param *kp)
{
int rc;
size_t index, len;
void *start, *end;
if (!MACHINE_IS_VM || !hvc_iucv_devices)
return -ENODEV;
rc = 0;
read_lock_bh(&hvc_iucv_filter_lock);
for (index = 0; index < hvc_iucv_filter_size; index++) {
start = hvc_iucv_filter + (8 * index);
end = memchr(start, ' ', 8);
len = (end) ? end - start : 8;
memcpy(buffer + rc, start, len);
rc += len;
buffer[rc++] = ',';
}
read_unlock_bh(&hvc_iucv_filter_lock);
if (rc)
buffer[--rc] = '\0'; /* replace last comma and update rc */
return rc;
}
#define param_check_vmidfilter(name, p) __param_check(name, p, void)
/**
* hvc_iucv_init() - z/VM IUCV HVC device driver initialization
*/
@ -884,19 +1092,44 @@ static int __init hvc_iucv_init(void)
int rc;
unsigned int i;
if (!hvc_iucv_devices)
return -ENODEV;
if (!MACHINE_IS_VM) {
pr_notice("The z/VM IUCV HVC device driver cannot "
"be used without z/VM\n");
return -ENODEV;
rc = -ENODEV;
goto out_error;
}
if (!hvc_iucv_devices)
return -ENODEV;
if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) {
pr_err("%lu is not a valid value for the hvc_iucv= "
"kernel parameter\n", hvc_iucv_devices);
return -EINVAL;
rc = -EINVAL;
goto out_error;
}
/* parse hvc_iucv_allow string and create z/VM user ID filter list */
if (hvc_iucv_filter_string) {
rc = hvc_iucv_setup_filter(hvc_iucv_filter_string);
switch (rc) {
case 0:
break;
case -ENOMEM:
pr_err("Allocating memory failed with "
"reason code=%d\n", 3);
goto out_error;
case -EINVAL:
pr_err("hvc_iucv_allow= does not specify a valid "
"z/VM user ID list\n");
goto out_error;
case -ENOSPC:
pr_err("hvc_iucv_allow= specifies too many "
"z/VM user IDs\n");
goto out_error;
default:
goto out_error;
}
}
hvc_iucv_buffer_cache = kmem_cache_create(KMSG_COMPONENT,
@ -904,7 +1137,8 @@ static int __init hvc_iucv_init(void)
0, 0, NULL);
if (!hvc_iucv_buffer_cache) {
pr_err("Allocating memory failed with reason code=%d\n", 1);
return -ENOMEM;
rc = -ENOMEM;
goto out_error;
}
hvc_iucv_mempool = mempool_create_slab_pool(MEMPOOL_MIN_NR,
@ -912,7 +1146,8 @@ static int __init hvc_iucv_init(void)
if (!hvc_iucv_mempool) {
pr_err("Allocating memory failed with reason code=%d\n", 2);
kmem_cache_destroy(hvc_iucv_buffer_cache);
return -ENOMEM;
rc = -ENOMEM;
goto out_error;
}
/* register the first terminal device as console
@ -956,6 +1191,8 @@ out_error_hvc:
out_error_memory:
mempool_destroy(hvc_iucv_mempool);
kmem_cache_destroy(hvc_iucv_buffer_cache);
out_error:
hvc_iucv_devices = 0; /* ensure that we do not provide any device */
return rc;
}
@ -971,3 +1208,4 @@ static int __init hvc_iucv_config(char *val)
device_initcall(hvc_iucv_init);
__setup("hvc_iucv=", hvc_iucv_config);
core_param(hvc_iucv_allow, hvc_iucv_filter, vmidfilter, 0640);