forked from Minki/linux
rfkill: make new event layout opt-in
Again new complaints surfaced that we had broken the ABI here,
although previously all the userspace tools had agreed that it
was their mistake and fixed it. Yet now there are cases (e.g.
RHEL) that want to run old userspace with newer kernels, and
thus are broken.
Since this is a bit of a whack-a-mole thing, change the whole
extensibility scheme of rfkill to no longer just rely on the
message lengths, but instead require userspace to opt in via a
new ioctl to a given maximum event size that it is willing to
understand.
By default, set that to RFKILL_EVENT_SIZE_V1 (8), so that the
behaviour for userspace not calling the ioctl will look as if
it's just running on an older kernel.
Fixes: 14486c8261
("rfkill: add a reason to the HW rfkill state")
Cc: stable@vger.kernel.org # 5.11+
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Kalle Valo <kvalo@kernel.org>
Link: https://lore.kernel.org/r/20220316212749.16491491b270.Ifcb1950998330a596f29a2a162e00b7546a1d6d0@changeid
This commit is contained in:
parent
a93ccb5c38
commit
54f586a915
@ -159,8 +159,16 @@ struct rfkill_event_ext {
|
||||
* old behaviour for all userspace, unless it explicitly opts in to the
|
||||
* rules outlined here by using the new &struct rfkill_event_ext.
|
||||
*
|
||||
* Userspace using &struct rfkill_event_ext must adhere to the following
|
||||
* rules
|
||||
* Additionally, some other userspace (bluez, g-s-d) was reading with a
|
||||
* large size but as streaming reads rather than message-based, or with
|
||||
* too strict checks for the returned size. So eventually, we completely
|
||||
* reverted this, and extended messages need to be opted in to by using
|
||||
* an ioctl:
|
||||
*
|
||||
* ioctl(fd, RFKILL_IOCTL_MAX_SIZE, sizeof(struct rfkill_event_ext));
|
||||
*
|
||||
* Userspace using &struct rfkill_event_ext and the ioctl must adhere to
|
||||
* the following rules:
|
||||
*
|
||||
* 1. accept short writes, optionally using them to detect that it's
|
||||
* running on an older kernel;
|
||||
@ -175,6 +183,8 @@ struct rfkill_event_ext {
|
||||
#define RFKILL_IOC_MAGIC 'R'
|
||||
#define RFKILL_IOC_NOINPUT 1
|
||||
#define RFKILL_IOCTL_NOINPUT _IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT)
|
||||
#define RFKILL_IOC_MAX_SIZE 2
|
||||
#define RFKILL_IOCTL_MAX_SIZE _IOW(RFKILL_IOC_MAGIC, RFKILL_IOC_EXT_SIZE, __u32)
|
||||
|
||||
/* and that's all userspace gets */
|
||||
|
||||
|
@ -78,6 +78,7 @@ struct rfkill_data {
|
||||
struct mutex mtx;
|
||||
wait_queue_head_t read_wait;
|
||||
bool input_handler;
|
||||
u8 max_size;
|
||||
};
|
||||
|
||||
|
||||
@ -1153,6 +1154,8 @@ static int rfkill_fop_open(struct inode *inode, struct file *file)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->max_size = RFKILL_EVENT_SIZE_V1;
|
||||
|
||||
INIT_LIST_HEAD(&data->events);
|
||||
mutex_init(&data->mtx);
|
||||
init_waitqueue_head(&data->read_wait);
|
||||
@ -1235,6 +1238,7 @@ static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
|
||||
list);
|
||||
|
||||
sz = min_t(unsigned long, sizeof(ev->ev), count);
|
||||
sz = min_t(unsigned long, sz, data->max_size);
|
||||
ret = sz;
|
||||
if (copy_to_user(buf, &ev->ev, sz))
|
||||
ret = -EFAULT;
|
||||
@ -1249,6 +1253,7 @@ static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
|
||||
static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct rfkill_data *data = file->private_data;
|
||||
struct rfkill *rfkill;
|
||||
struct rfkill_event_ext ev;
|
||||
int ret;
|
||||
@ -1263,6 +1268,7 @@ static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
|
||||
* our API version even in a write() call, if it cares.
|
||||
*/
|
||||
count = min(count, sizeof(ev));
|
||||
count = min_t(size_t, count, data->max_size);
|
||||
if (copy_from_user(&ev, buf, count))
|
||||
return -EFAULT;
|
||||
|
||||
@ -1322,31 +1328,47 @@ static int rfkill_fop_release(struct inode *inode, struct file *file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RFKILL_INPUT
|
||||
static long rfkill_fop_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct rfkill_data *data = file->private_data;
|
||||
int ret = -ENOSYS;
|
||||
u32 size;
|
||||
|
||||
if (_IOC_TYPE(cmd) != RFKILL_IOC_MAGIC)
|
||||
return -ENOSYS;
|
||||
|
||||
if (_IOC_NR(cmd) != RFKILL_IOC_NOINPUT)
|
||||
return -ENOSYS;
|
||||
|
||||
mutex_lock(&data->mtx);
|
||||
|
||||
if (!data->input_handler) {
|
||||
if (atomic_inc_return(&rfkill_input_disabled) == 1)
|
||||
printk(KERN_DEBUG "rfkill: input handler disabled\n");
|
||||
data->input_handler = true;
|
||||
switch (_IOC_NR(cmd)) {
|
||||
#ifdef CONFIG_RFKILL_INPUT
|
||||
case RFKILL_IOC_NOINPUT:
|
||||
if (!data->input_handler) {
|
||||
if (atomic_inc_return(&rfkill_input_disabled) == 1)
|
||||
printk(KERN_DEBUG "rfkill: input handler disabled\n");
|
||||
data->input_handler = true;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
#endif
|
||||
case RFKILL_IOC_MAX_SIZE:
|
||||
if (get_user(size, (__u32 __user *)arg)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
if (size < RFKILL_EVENT_SIZE_V1 || size > U8_MAX) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
data->max_size = size;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->mtx);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct file_operations rfkill_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
@ -1355,10 +1377,8 @@ static const struct file_operations rfkill_fops = {
|
||||
.write = rfkill_fop_write,
|
||||
.poll = rfkill_fop_poll,
|
||||
.release = rfkill_fop_release,
|
||||
#ifdef CONFIG_RFKILL_INPUT
|
||||
.unlocked_ioctl = rfkill_fop_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
#endif
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user