Commit 431429ff788598a19c1a193b9fca3961b7f55916

Authored by Hendrik Brueckner
Committed by Martin Schwidefsky
1 parent 82f3a79bc6

[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>

Showing 2 changed files with 249 additions and 8 deletions Side-by-side Diff

Documentation/kernel-parameters.txt
... ... @@ -829,6 +829,9 @@
829 829  
830 830 hvc_iucv= [S390] Number of z/VM IUCV hypervisor console (HVC)
831 831 terminal devices. Valid values: 0..8
  832 + hvc_iucv_allow= [S390] Comma-separated list of z/VM user IDs.
  833 + If specified, z/VM IUCV HVC accepts connections
  834 + from listed z/VM user IDs only.
832 835  
833 836 i8042.debug [HW] Toggle i8042 debug mode
834 837 i8042.direct [HW] Put keyboard port into non-translated mode
drivers/char/hvc_iucv.c
... ... @@ -13,10 +13,11 @@
13 13  
14 14 #include <linux/types.h>
15 15 #include <asm/ebcdic.h>
  16 +#include <linux/ctype.h>
16 17 #include <linux/delay.h>
17 18 #include <linux/init.h>
18 19 #include <linux/mempool.h>
19   -#include <linux/module.h>
  20 +#include <linux/moduleparam.h>
20 21 #include <linux/tty.h>
21 22 #include <linux/wait.h>
22 23 #include <net/iucv/iucv.h>
... ... @@ -95,6 +96,12 @@
95 96 /* Array of allocated hvc iucv tty lines... */
96 97 static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES];
97 98 #define IUCV_HVC_CON_IDX (0)
  99 +/* List of z/VM user ID filter entries (struct iucv_vmid_filter) */
  100 +#define MAX_VMID_FILTER (500)
  101 +static size_t hvc_iucv_filter_size;
  102 +static void *hvc_iucv_filter;
  103 +static const char *hvc_iucv_filter_string;
  104 +static DEFINE_RWLOCK(hvc_iucv_filter_lock);
98 105  
99 106 /* Kmem cache and mempool for iucv_tty_buffer elements */
100 107 static struct kmem_cache *hvc_iucv_buffer_cache;
... ... @@ -618,6 +625,27 @@
618 625 }
619 626  
620 627 /**
  628 + * hvc_iucv_filter_connreq() - Filter connection request based on z/VM user ID
  629 + * @ipvmid: Originating z/VM user ID (right padded with blanks)
  630 + *
  631 + * Returns 0 if the z/VM user ID @ipvmid is allowed to connection, otherwise
  632 + * non-zero.
  633 + */
  634 +static int hvc_iucv_filter_connreq(u8 ipvmid[8])
  635 +{
  636 + size_t i;
  637 +
  638 + /* Note: default policy is ACCEPT if no filter is set */
  639 + if (!hvc_iucv_filter_size)
  640 + return 0;
  641 +
  642 + for (i = 0; i < hvc_iucv_filter_size; i++)
  643 + if (0 == memcmp(ipvmid, hvc_iucv_filter + (8 * i), 8))
  644 + return 0;
  645 + return 1;
  646 +}
  647 +
  648 +/**
621 649 * hvc_iucv_path_pending() - IUCV handler to process a connection request.
622 650 * @path: Pending path (struct iucv_path)
623 651 * @ipvmid: z/VM system identifier of originator
... ... @@ -641,6 +669,7 @@
641 669 {
642 670 struct hvc_iucv_private *priv;
643 671 u8 nuser_data[16];
  672 + u8 vm_user_id[9];
644 673 int i, rc;
645 674  
646 675 priv = NULL;
... ... @@ -653,6 +682,20 @@
653 682 if (!priv)
654 683 return -ENODEV;
655 684  
  685 + /* Enforce that ipvmid is allowed to connect to us */
  686 + read_lock(&hvc_iucv_filter_lock);
  687 + rc = hvc_iucv_filter_connreq(ipvmid);
  688 + read_unlock(&hvc_iucv_filter_lock);
  689 + if (rc) {
  690 + iucv_path_sever(path, ipuser);
  691 + iucv_path_free(path);
  692 + memcpy(vm_user_id, ipvmid, 8);
  693 + vm_user_id[8] = 0;
  694 + pr_info("A connection request from z/VM user ID %s "
  695 + "was refused\n", vm_user_id);
  696 + return 0;
  697 + }
  698 +
656 699 spin_lock(&priv->lock);
657 700  
658 701 /* If the terminal is already connected or being severed, then sever
... ... @@ -877,6 +920,171 @@
877 920 }
878 921  
879 922 /**
  923 + * hvc_iucv_parse_filter() - Parse filter for a single z/VM user ID
  924 + * @filter: String containing a comma-separated list of z/VM user IDs
  925 + */
  926 +static const char *hvc_iucv_parse_filter(const char *filter, char *dest)
  927 +{
  928 + const char *nextdelim, *residual;
  929 + size_t len;
  930 +
  931 + nextdelim = strchr(filter, ',');
  932 + if (nextdelim) {
  933 + len = nextdelim - filter;
  934 + residual = nextdelim + 1;
  935 + } else {
  936 + len = strlen(filter);
  937 + residual = filter + len;
  938 + }
  939 +
  940 + if (len == 0)
  941 + return ERR_PTR(-EINVAL);
  942 +
  943 + /* check for '\n' (if called from sysfs) */
  944 + if (filter[len - 1] == '\n')
  945 + len--;
  946 +
  947 + if (len > 8)
  948 + return ERR_PTR(-EINVAL);
  949 +
  950 + /* pad with blanks and save upper case version of user ID */
  951 + memset(dest, ' ', 8);
  952 + while (len--)
  953 + dest[len] = toupper(filter[len]);
  954 + return residual;
  955 +}
  956 +
  957 +/**
  958 + * hvc_iucv_setup_filter() - Set up z/VM user ID filter
  959 + * @filter: String consisting of a comma-separated list of z/VM user IDs
  960 + *
  961 + * The function parses the @filter string and creates an array containing
  962 + * the list of z/VM user ID filter entries.
  963 + * Return code 0 means success, -EINVAL if the filter is syntactically
  964 + * incorrect, -ENOMEM if there was not enough memory to allocate the
  965 + * filter list array, or -ENOSPC if too many z/VM user IDs have been specified.
  966 + */
  967 +static int hvc_iucv_setup_filter(const char *val)
  968 +{
  969 + const char *residual;
  970 + int err;
  971 + size_t size, count;
  972 + void *array, *old_filter;
  973 +
  974 + count = strlen(val);
  975 + if (count == 0 || (count == 1 && val[0] == '\n')) {
  976 + size = 0;
  977 + array = NULL;
  978 + goto out_replace_filter; /* clear filter */
  979 + }
  980 +
  981 + /* count user IDs in order to allocate sufficient memory */
  982 + size = 1;
  983 + residual = val;
  984 + while ((residual = strchr(residual, ',')) != NULL) {
  985 + residual++;
  986 + size++;
  987 + }
  988 +
  989 + /* check if the specified list exceeds the filter limit */
  990 + if (size > MAX_VMID_FILTER)
  991 + return -ENOSPC;
  992 +
  993 + array = kzalloc(size * 8, GFP_KERNEL);
  994 + if (!array)
  995 + return -ENOMEM;
  996 +
  997 + count = size;
  998 + residual = val;
  999 + while (*residual && count) {
  1000 + residual = hvc_iucv_parse_filter(residual,
  1001 + array + ((size - count) * 8));
  1002 + if (IS_ERR(residual)) {
  1003 + err = PTR_ERR(residual);
  1004 + kfree(array);
  1005 + goto out_err;
  1006 + }
  1007 + count--;
  1008 + }
  1009 +
  1010 +out_replace_filter:
  1011 + write_lock_bh(&hvc_iucv_filter_lock);
  1012 + old_filter = hvc_iucv_filter;
  1013 + hvc_iucv_filter_size = size;
  1014 + hvc_iucv_filter = array;
  1015 + write_unlock_bh(&hvc_iucv_filter_lock);
  1016 + kfree(old_filter);
  1017 +
  1018 + err = 0;
  1019 +out_err:
  1020 + return err;
  1021 +}
  1022 +
  1023 +/**
  1024 + * param_set_vmidfilter() - Set z/VM user ID filter parameter
  1025 + * @val: String consisting of a comma-separated list of z/VM user IDs
  1026 + * @kp: Kernel parameter pointing to hvc_iucv_filter array
  1027 + *
  1028 + * The function sets up the z/VM user ID filter specified as comma-separated
  1029 + * list of user IDs in @val.
  1030 + * Note: If it is called early in the boot process, @val is stored and
  1031 + * parsed later in hvc_iucv_init().
  1032 + */
  1033 +static int param_set_vmidfilter(const char *val, struct kernel_param *kp)
  1034 +{
  1035 + int rc;
  1036 +
  1037 + if (!MACHINE_IS_VM || !hvc_iucv_devices)
  1038 + return -ENODEV;
  1039 +
  1040 + if (!val)
  1041 + return -EINVAL;
  1042 +
  1043 + rc = 0;
  1044 + if (slab_is_available())
  1045 + rc = hvc_iucv_setup_filter(val);
  1046 + else
  1047 + hvc_iucv_filter_string = val; /* defer... */
  1048 + return rc;
  1049 +}
  1050 +
  1051 +/**
  1052 + * param_get_vmidfilter() - Get z/VM user ID filter
  1053 + * @buffer: Buffer to store z/VM user ID filter,
  1054 + * (buffer size assumption PAGE_SIZE)
  1055 + * @kp: Kernel parameter pointing to the hvc_iucv_filter array
  1056 + *
  1057 + * The function stores the filter as a comma-separated list of z/VM user IDs
  1058 + * in @buffer. Typically, sysfs routines call this function for attr show.
  1059 + */
  1060 +static int param_get_vmidfilter(char *buffer, struct kernel_param *kp)
  1061 +{
  1062 + int rc;
  1063 + size_t index, len;
  1064 + void *start, *end;
  1065 +
  1066 + if (!MACHINE_IS_VM || !hvc_iucv_devices)
  1067 + return -ENODEV;
  1068 +
  1069 + rc = 0;
  1070 + read_lock_bh(&hvc_iucv_filter_lock);
  1071 + for (index = 0; index < hvc_iucv_filter_size; index++) {
  1072 + start = hvc_iucv_filter + (8 * index);
  1073 + end = memchr(start, ' ', 8);
  1074 + len = (end) ? end - start : 8;
  1075 + memcpy(buffer + rc, start, len);
  1076 + rc += len;
  1077 + buffer[rc++] = ',';
  1078 + }
  1079 + read_unlock_bh(&hvc_iucv_filter_lock);
  1080 + if (rc)
  1081 + buffer[--rc] = '\0'; /* replace last comma and update rc */
  1082 + return rc;
  1083 +}
  1084 +
  1085 +#define param_check_vmidfilter(name, p) __param_check(name, p, void)
  1086 +
  1087 +/**
880 1088 * hvc_iucv_init() - z/VM IUCV HVC device driver initialization
881 1089 */
882 1090 static int __init hvc_iucv_init(void)
883 1091  
884 1092  
885 1093  
886 1094  
887 1095  
... ... @@ -884,27 +1092,53 @@
884 1092 int rc;
885 1093 unsigned int i;
886 1094  
  1095 + if (!hvc_iucv_devices)
  1096 + return -ENODEV;
  1097 +
887 1098 if (!MACHINE_IS_VM) {
888 1099 pr_notice("The z/VM IUCV HVC device driver cannot "
889 1100 "be used without z/VM\n");
890   - return -ENODEV;
  1101 + rc = -ENODEV;
  1102 + goto out_error;
891 1103 }
892 1104  
893   - if (!hvc_iucv_devices)
894   - return -ENODEV;
895   -
896 1105 if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) {
897 1106 pr_err("%lu is not a valid value for the hvc_iucv= "
898 1107 "kernel parameter\n", hvc_iucv_devices);
899   - return -EINVAL;
  1108 + rc = -EINVAL;
  1109 + goto out_error;
900 1110 }
901 1111  
  1112 + /* parse hvc_iucv_allow string and create z/VM user ID filter list */
  1113 + if (hvc_iucv_filter_string) {
  1114 + rc = hvc_iucv_setup_filter(hvc_iucv_filter_string);
  1115 + switch (rc) {
  1116 + case 0:
  1117 + break;
  1118 + case -ENOMEM:
  1119 + pr_err("Allocating memory failed with "
  1120 + "reason code=%d\n", 3);
  1121 + goto out_error;
  1122 + case -EINVAL:
  1123 + pr_err("hvc_iucv_allow= does not specify a valid "
  1124 + "z/VM user ID list\n");
  1125 + goto out_error;
  1126 + case -ENOSPC:
  1127 + pr_err("hvc_iucv_allow= specifies too many "
  1128 + "z/VM user IDs\n");
  1129 + goto out_error;
  1130 + default:
  1131 + goto out_error;
  1132 + }
  1133 + }
  1134 +
902 1135 hvc_iucv_buffer_cache = kmem_cache_create(KMSG_COMPONENT,
903 1136 sizeof(struct iucv_tty_buffer),
904 1137 0, 0, NULL);
905 1138 if (!hvc_iucv_buffer_cache) {
906 1139 pr_err("Allocating memory failed with reason code=%d\n", 1);
907   - return -ENOMEM;
  1140 + rc = -ENOMEM;
  1141 + goto out_error;
908 1142 }
909 1143  
910 1144 hvc_iucv_mempool = mempool_create_slab_pool(MEMPOOL_MIN_NR,
... ... @@ -912,7 +1146,8 @@
912 1146 if (!hvc_iucv_mempool) {
913 1147 pr_err("Allocating memory failed with reason code=%d\n", 2);
914 1148 kmem_cache_destroy(hvc_iucv_buffer_cache);
915   - return -ENOMEM;
  1149 + rc = -ENOMEM;
  1150 + goto out_error;
916 1151 }
917 1152  
918 1153 /* register the first terminal device as console
... ... @@ -956,6 +1191,8 @@
956 1191 out_error_memory:
957 1192 mempool_destroy(hvc_iucv_mempool);
958 1193 kmem_cache_destroy(hvc_iucv_buffer_cache);
  1194 +out_error:
  1195 + hvc_iucv_devices = 0; /* ensure that we do not provide any device */
959 1196 return rc;
960 1197 }
961 1198  
... ... @@ -971,4 +1208,5 @@
971 1208  
972 1209 device_initcall(hvc_iucv_init);
973 1210 __setup("hvc_iucv=", hvc_iucv_config);
  1211 +core_param(hvc_iucv_allow, hvc_iucv_filter, vmidfilter, 0640);