diff options
author | Hendrik Brueckner <brueckner@linux.vnet.ibm.com> | 2009-03-26 10:23:55 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2009-03-26 10:24:08 -0400 |
commit | 431429ff788598a19c1a193b9fca3961b7f55916 (patch) | |
tree | ba860720c0b2d329cf1587e23f52108e428ad71b | |
parent | 82f3a79bc6b50ab82744ebc32efba31c78dbccf7 (diff) |
[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>
-rw-r--r-- | Documentation/kernel-parameters.txt | 3 | ||||
-rw-r--r-- | drivers/char/hvc_iucv.c | 254 |
2 files changed, 249 insertions, 8 deletions
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 54f21a5c262b..9f932a76a940 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
@@ -829,6 +829,9 @@ and is between 256 and 4096 characters. It is defined in the file | |||
829 | 829 | ||
830 | hvc_iucv= [S390] Number of z/VM IUCV hypervisor console (HVC) | 830 | hvc_iucv= [S390] Number of z/VM IUCV hypervisor console (HVC) |
831 | terminal devices. Valid values: 0..8 | 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 | i8042.debug [HW] Toggle i8042 debug mode | 836 | i8042.debug [HW] Toggle i8042 debug mode |
834 | i8042.direct [HW] Put keyboard port into non-translated mode | 837 | i8042.direct [HW] Put keyboard port into non-translated mode |
diff --git a/drivers/char/hvc_iucv.c b/drivers/char/hvc_iucv.c index 146be5a60947..54481a887769 100644 --- a/drivers/char/hvc_iucv.c +++ b/drivers/char/hvc_iucv.c | |||
@@ -13,10 +13,11 @@ | |||
13 | 13 | ||
14 | #include <linux/types.h> | 14 | #include <linux/types.h> |
15 | #include <asm/ebcdic.h> | 15 | #include <asm/ebcdic.h> |
16 | #include <linux/ctype.h> | ||
16 | #include <linux/delay.h> | 17 | #include <linux/delay.h> |
17 | #include <linux/init.h> | 18 | #include <linux/init.h> |
18 | #include <linux/mempool.h> | 19 | #include <linux/mempool.h> |
19 | #include <linux/module.h> | 20 | #include <linux/moduleparam.h> |
20 | #include <linux/tty.h> | 21 | #include <linux/tty.h> |
21 | #include <linux/wait.h> | 22 | #include <linux/wait.h> |
22 | #include <net/iucv/iucv.h> | 23 | #include <net/iucv/iucv.h> |
@@ -95,6 +96,12 @@ static unsigned long hvc_iucv_devices = 1; | |||
95 | /* Array of allocated hvc iucv tty lines... */ | 96 | /* Array of allocated hvc iucv tty lines... */ |
96 | static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES]; | 97 | static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES]; |
97 | #define IUCV_HVC_CON_IDX (0) | 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 | /* Kmem cache and mempool for iucv_tty_buffer elements */ | 106 | /* Kmem cache and mempool for iucv_tty_buffer elements */ |
100 | static struct kmem_cache *hvc_iucv_buffer_cache; | 107 | static struct kmem_cache *hvc_iucv_buffer_cache; |
@@ -618,6 +625,27 @@ static void hvc_iucv_notifier_del(struct hvc_struct *hp, int id) | |||
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 | * hvc_iucv_path_pending() - IUCV handler to process a connection request. | 649 | * hvc_iucv_path_pending() - IUCV handler to process a connection request. |
622 | * @path: Pending path (struct iucv_path) | 650 | * @path: Pending path (struct iucv_path) |
623 | * @ipvmid: z/VM system identifier of originator | 651 | * @ipvmid: z/VM system identifier of originator |
@@ -641,6 +669,7 @@ static int hvc_iucv_path_pending(struct iucv_path *path, | |||
641 | { | 669 | { |
642 | struct hvc_iucv_private *priv; | 670 | struct hvc_iucv_private *priv; |
643 | u8 nuser_data[16]; | 671 | u8 nuser_data[16]; |
672 | u8 vm_user_id[9]; | ||
644 | int i, rc; | 673 | int i, rc; |
645 | 674 | ||
646 | priv = NULL; | 675 | priv = NULL; |
@@ -653,6 +682,20 @@ static int hvc_iucv_path_pending(struct iucv_path *path, | |||
653 | if (!priv) | 682 | if (!priv) |
654 | return -ENODEV; | 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 | spin_lock(&priv->lock); | 699 | spin_lock(&priv->lock); |
657 | 700 | ||
658 | /* If the terminal is already connected or being severed, then sever | 701 | /* If the terminal is already connected or being severed, then sever |
@@ -877,6 +920,171 @@ static int __init hvc_iucv_alloc(int id, unsigned int is_console) | |||
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 | * hvc_iucv_init() - z/VM IUCV HVC device driver initialization | 1088 | * hvc_iucv_init() - z/VM IUCV HVC device driver initialization |
881 | */ | 1089 | */ |
882 | static int __init hvc_iucv_init(void) | 1090 | static int __init hvc_iucv_init(void) |
@@ -884,19 +1092,44 @@ static int __init hvc_iucv_init(void) | |||
884 | int rc; | 1092 | int rc; |
885 | unsigned int i; | 1093 | unsigned int i; |
886 | 1094 | ||
1095 | if (!hvc_iucv_devices) | ||
1096 | return -ENODEV; | ||
1097 | |||
887 | if (!MACHINE_IS_VM) { | 1098 | if (!MACHINE_IS_VM) { |
888 | pr_notice("The z/VM IUCV HVC device driver cannot " | 1099 | pr_notice("The z/VM IUCV HVC device driver cannot " |
889 | "be used without z/VM\n"); | 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 | if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) { | 1105 | if (hvc_iucv_devices > MAX_HVC_IUCV_LINES) { |
897 | pr_err("%lu is not a valid value for the hvc_iucv= " | 1106 | pr_err("%lu is not a valid value for the hvc_iucv= " |
898 | "kernel parameter\n", hvc_iucv_devices); | 1107 | "kernel parameter\n", hvc_iucv_devices); |
899 | return -EINVAL; | 1108 | rc = -EINVAL; |
1109 | goto out_error; | ||
1110 | } | ||
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 | } | ||
900 | } | 1133 | } |
901 | 1134 | ||
902 | hvc_iucv_buffer_cache = kmem_cache_create(KMSG_COMPONENT, | 1135 | hvc_iucv_buffer_cache = kmem_cache_create(KMSG_COMPONENT, |
@@ -904,7 +1137,8 @@ static int __init hvc_iucv_init(void) | |||
904 | 0, 0, NULL); | 1137 | 0, 0, NULL); |
905 | if (!hvc_iucv_buffer_cache) { | 1138 | if (!hvc_iucv_buffer_cache) { |
906 | pr_err("Allocating memory failed with reason code=%d\n", 1); | 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 | hvc_iucv_mempool = mempool_create_slab_pool(MEMPOOL_MIN_NR, | 1144 | hvc_iucv_mempool = mempool_create_slab_pool(MEMPOOL_MIN_NR, |
@@ -912,7 +1146,8 @@ static int __init hvc_iucv_init(void) | |||
912 | if (!hvc_iucv_mempool) { | 1146 | if (!hvc_iucv_mempool) { |
913 | pr_err("Allocating memory failed with reason code=%d\n", 2); | 1147 | pr_err("Allocating memory failed with reason code=%d\n", 2); |
914 | kmem_cache_destroy(hvc_iucv_buffer_cache); | 1148 | kmem_cache_destroy(hvc_iucv_buffer_cache); |
915 | return -ENOMEM; | 1149 | rc = -ENOMEM; |
1150 | goto out_error; | ||
916 | } | 1151 | } |
917 | 1152 | ||
918 | /* register the first terminal device as console | 1153 | /* register the first terminal device as console |
@@ -956,6 +1191,8 @@ out_error_hvc: | |||
956 | out_error_memory: | 1191 | out_error_memory: |
957 | mempool_destroy(hvc_iucv_mempool); | 1192 | mempool_destroy(hvc_iucv_mempool); |
958 | kmem_cache_destroy(hvc_iucv_buffer_cache); | 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 | return rc; | 1196 | return rc; |
960 | } | 1197 | } |
961 | 1198 | ||
@@ -971,3 +1208,4 @@ static int __init hvc_iucv_config(char *val) | |||
971 | 1208 | ||
972 | device_initcall(hvc_iucv_init); | 1209 | device_initcall(hvc_iucv_init); |
973 | __setup("hvc_iucv=", hvc_iucv_config); | 1210 | __setup("hvc_iucv=", hvc_iucv_config); |
1211 | core_param(hvc_iucv_allow, hvc_iucv_filter, vmidfilter, 0640); | ||