Commit 431429ff788598a19c1a193b9fca3961b7f55916
Committed by
Martin Schwidefsky
1 parent
82f3a79bc6
Exists in
master
and in
4 other branches
[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); |