wext-priv.c (7027B)
1/* 2 * This file implement the Wireless Extensions priv API. 3 * 4 * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> 5 * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. 6 * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> 7 * 8 * (As all part of the Linux kernel, this file is GPL) 9 */ 10#include <linux/slab.h> 11#include <linux/wireless.h> 12#include <linux/netdevice.h> 13#include <net/iw_handler.h> 14#include <net/wext.h> 15 16int iw_handler_get_private(struct net_device * dev, 17 struct iw_request_info * info, 18 union iwreq_data * wrqu, 19 char * extra) 20{ 21 /* Check if the driver has something to export */ 22 if ((dev->wireless_handlers->num_private_args == 0) || 23 (dev->wireless_handlers->private_args == NULL)) 24 return -EOPNOTSUPP; 25 26 /* Check if there is enough buffer up there */ 27 if (wrqu->data.length < dev->wireless_handlers->num_private_args) { 28 /* User space can't know in advance how large the buffer 29 * needs to be. Give it a hint, so that we can support 30 * any size buffer we want somewhat efficiently... */ 31 wrqu->data.length = dev->wireless_handlers->num_private_args; 32 return -E2BIG; 33 } 34 35 /* Set the number of available ioctls. */ 36 wrqu->data.length = dev->wireless_handlers->num_private_args; 37 38 /* Copy structure to the user buffer. */ 39 memcpy(extra, dev->wireless_handlers->private_args, 40 sizeof(struct iw_priv_args) * wrqu->data.length); 41 42 return 0; 43} 44 45/* Size (in bytes) of the various private data types */ 46static const char iw_priv_type_size[] = { 47 0, /* IW_PRIV_TYPE_NONE */ 48 1, /* IW_PRIV_TYPE_BYTE */ 49 1, /* IW_PRIV_TYPE_CHAR */ 50 0, /* Not defined */ 51 sizeof(__u32), /* IW_PRIV_TYPE_INT */ 52 sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ 53 sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ 54 0, /* Not defined */ 55}; 56 57static int get_priv_size(__u16 args) 58{ 59 int num = args & IW_PRIV_SIZE_MASK; 60 int type = (args & IW_PRIV_TYPE_MASK) >> 12; 61 62 return num * iw_priv_type_size[type]; 63} 64 65static int adjust_priv_size(__u16 args, struct iw_point *iwp) 66{ 67 int num = iwp->length; 68 int max = args & IW_PRIV_SIZE_MASK; 69 int type = (args & IW_PRIV_TYPE_MASK) >> 12; 70 71 /* Make sure the driver doesn't goof up */ 72 if (max < num) 73 num = max; 74 75 return num * iw_priv_type_size[type]; 76} 77 78/* 79 * Wrapper to call a private Wireless Extension handler. 80 * We do various checks and also take care of moving data between 81 * user space and kernel space. 82 * It's not as nice and slimline as the standard wrapper. The cause 83 * is struct iw_priv_args, which was not really designed for the 84 * job we are going here. 85 * 86 * IMPORTANT : This function prevent to set and get data on the same 87 * IOCTL and enforce the SET/GET convention. Not doing it would be 88 * far too hairy... 89 * If you need to set and get data at the same time, please don't use 90 * a iw_handler but process it in your ioctl handler (i.e. use the 91 * old driver API). 92 */ 93static int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd, 94 const struct iw_priv_args **descrp) 95{ 96 const struct iw_priv_args *descr; 97 int i, extra_size; 98 99 descr = NULL; 100 for (i = 0; i < dev->wireless_handlers->num_private_args; i++) { 101 if (cmd == dev->wireless_handlers->private_args[i].cmd) { 102 descr = &dev->wireless_handlers->private_args[i]; 103 break; 104 } 105 } 106 107 extra_size = 0; 108 if (descr) { 109 if (IW_IS_SET(cmd)) { 110 int offset = 0; /* For sub-ioctls */ 111 /* Check for sub-ioctl handler */ 112 if (descr->name[0] == '\0') 113 /* Reserve one int for sub-ioctl index */ 114 offset = sizeof(__u32); 115 116 /* Size of set arguments */ 117 extra_size = get_priv_size(descr->set_args); 118 119 /* Does it fits in iwr ? */ 120 if ((descr->set_args & IW_PRIV_SIZE_FIXED) && 121 ((extra_size + offset) <= IFNAMSIZ)) 122 extra_size = 0; 123 } else { 124 /* Size of get arguments */ 125 extra_size = get_priv_size(descr->get_args); 126 127 /* Does it fits in iwr ? */ 128 if ((descr->get_args & IW_PRIV_SIZE_FIXED) && 129 (extra_size <= IFNAMSIZ)) 130 extra_size = 0; 131 } 132 } 133 *descrp = descr; 134 return extra_size; 135} 136 137static int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd, 138 const struct iw_priv_args *descr, 139 iw_handler handler, struct net_device *dev, 140 struct iw_request_info *info, int extra_size) 141{ 142 char *extra; 143 int err; 144 145 /* Check what user space is giving us */ 146 if (IW_IS_SET(cmd)) { 147 if (!iwp->pointer && iwp->length != 0) 148 return -EFAULT; 149 150 if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK)) 151 return -E2BIG; 152 } else if (!iwp->pointer) 153 return -EFAULT; 154 155 extra = kzalloc(extra_size, GFP_KERNEL); 156 if (!extra) 157 return -ENOMEM; 158 159 /* If it is a SET, get all the extra data in here */ 160 if (IW_IS_SET(cmd) && (iwp->length != 0)) { 161 if (copy_from_user(extra, iwp->pointer, extra_size)) { 162 err = -EFAULT; 163 goto out; 164 } 165 } 166 167 /* Call the handler */ 168 err = handler(dev, info, (union iwreq_data *) iwp, extra); 169 170 /* If we have something to return to the user */ 171 if (!err && IW_IS_GET(cmd)) { 172 /* Adjust for the actual length if it's variable, 173 * avoid leaking kernel bits outside. 174 */ 175 if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) 176 extra_size = adjust_priv_size(descr->get_args, iwp); 177 178 if (copy_to_user(iwp->pointer, extra, extra_size)) 179 err = -EFAULT; 180 } 181 182out: 183 kfree(extra); 184 return err; 185} 186 187int ioctl_private_call(struct net_device *dev, struct iwreq *iwr, 188 unsigned int cmd, struct iw_request_info *info, 189 iw_handler handler) 190{ 191 int extra_size = 0, ret = -EINVAL; 192 const struct iw_priv_args *descr; 193 194 extra_size = get_priv_descr_and_size(dev, cmd, &descr); 195 196 /* Check if we have a pointer to user space data or not. */ 197 if (extra_size == 0) { 198 /* No extra arguments. Trivial to handle */ 199 ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 200 } else { 201 ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr, 202 handler, dev, info, extra_size); 203 } 204 205 /* Call commit handler if needed and defined */ 206 if (ret == -EIWCOMMIT) 207 ret = call_commit_handler(dev); 208 209 return ret; 210} 211 212#ifdef CONFIG_COMPAT 213int compat_private_call(struct net_device *dev, struct iwreq *iwr, 214 unsigned int cmd, struct iw_request_info *info, 215 iw_handler handler) 216{ 217 const struct iw_priv_args *descr; 218 int ret, extra_size; 219 220 extra_size = get_priv_descr_and_size(dev, cmd, &descr); 221 222 /* Check if we have a pointer to user space data or not. */ 223 if (extra_size == 0) { 224 /* No extra arguments. Trivial to handle */ 225 ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); 226 } else { 227 struct compat_iw_point *iwp_compat; 228 struct iw_point iwp; 229 230 iwp_compat = (struct compat_iw_point *) &iwr->u.data; 231 iwp.pointer = compat_ptr(iwp_compat->pointer); 232 iwp.length = iwp_compat->length; 233 iwp.flags = iwp_compat->flags; 234 235 ret = ioctl_private_iw_point(&iwp, cmd, descr, 236 handler, dev, info, extra_size); 237 238 iwp_compat->pointer = ptr_to_compat(iwp.pointer); 239 iwp_compat->length = iwp.length; 240 iwp_compat->flags = iwp.flags; 241 } 242 243 /* Call commit handler if needed and defined */ 244 if (ret == -EIWCOMMIT) 245 ret = call_commit_handler(dev); 246 247 return ret; 248} 249#endif