mirror of
https://github.com/nxp-imx/linux-imx.git
synced 2025-07-06 01:15:20 +02:00

commit02ac3a9ef3
upstream. The recent fix for array out-of-bounds accesses replaced sprintf() calls blindly with snprintf(). However, since snprintf() returns the would-be-printed size, not the actually output size, the length calculation can still go over the given limit. Use scnprintf() instead of snprintf(), which returns the actually output letters, for addressing the potential out-of-bounds access properly. Fixes:ab11dac93d
("dev/parport: fix the array out-of-bounds risk") Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://lore.kernel.org/r/20240920103318.19271-1-tiwai@suse.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
639 lines
14 KiB
C
639 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Sysctl interface for parport devices.
|
|
*
|
|
* Authors: David Campbell
|
|
* Tim Waugh <tim@cyberelk.demon.co.uk>
|
|
* Philip Blundell <philb@gnu.org>
|
|
* Andrea Arcangeli
|
|
* Riccardo Facchetti <fizban@tin.it>
|
|
*
|
|
* based on work by Grant Guenther <grant@torque.net>
|
|
* and Philip Blundell
|
|
*
|
|
* Cleaned up include files - Russell King <linux@arm.uk.linux.org>
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/parport.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/sysctl.h>
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
|
|
|
|
#define PARPORT_MIN_TIMESLICE_VALUE 1ul
|
|
#define PARPORT_MAX_TIMESLICE_VALUE ((unsigned long) HZ)
|
|
#define PARPORT_MIN_SPINTIME_VALUE 1
|
|
#define PARPORT_MAX_SPINTIME_VALUE 1000
|
|
/*
|
|
* PARPORT_BASE_* is the size of the known parts of the sysctl path
|
|
* in dev/partport/%s/devices/%s. "dev/parport/"(12), "/devices/"(9
|
|
* and null char(1).
|
|
*/
|
|
#define PARPORT_BASE_PATH_SIZE 13
|
|
#define PARPORT_BASE_DEVICES_PATH_SIZE 22
|
|
|
|
static int do_active_device(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport *port = (struct parport *)table->extra1;
|
|
char buffer[256];
|
|
struct pardevice *dev;
|
|
int len = 0;
|
|
|
|
if (write) /* can't happen anyway */
|
|
return -EACCES;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
for (dev = port->devices; dev ; dev = dev->next) {
|
|
if(dev == port->cad) {
|
|
len += scnprintf(buffer, sizeof(buffer), "%s\n", dev->name);
|
|
}
|
|
}
|
|
|
|
if(!len) {
|
|
len += scnprintf(buffer, sizeof(buffer), "%s\n", "none");
|
|
}
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PARPORT_1284
|
|
static int do_autoprobe(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport_device_info *info = table->extra2;
|
|
const char *str;
|
|
char buffer[256];
|
|
int len = 0;
|
|
|
|
if (write) /* permissions stop this */
|
|
return -EACCES;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if ((str = info->class_name) != NULL)
|
|
len += scnprintf (buffer + len, sizeof(buffer) - len, "CLASS:%s;\n", str);
|
|
|
|
if ((str = info->model) != NULL)
|
|
len += scnprintf (buffer + len, sizeof(buffer) - len, "MODEL:%s;\n", str);
|
|
|
|
if ((str = info->mfr) != NULL)
|
|
len += scnprintf (buffer + len, sizeof(buffer) - len, "MANUFACTURER:%s;\n", str);
|
|
|
|
if ((str = info->description) != NULL)
|
|
len += scnprintf (buffer + len, sizeof(buffer) - len, "DESCRIPTION:%s;\n", str);
|
|
|
|
if ((str = info->cmdset) != NULL)
|
|
len += scnprintf (buffer + len, sizeof(buffer) - len, "COMMAND SET:%s;\n", str);
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
#endif /* IEEE1284.3 support. */
|
|
|
|
static int do_hardware_base_addr(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport *port = (struct parport *)table->extra1;
|
|
char buffer[64];
|
|
int len = 0;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (write) /* permissions prevent this anyway */
|
|
return -EACCES;
|
|
|
|
len += scnprintf (buffer, sizeof(buffer), "%lu\t%lu\n", port->base, port->base_hi);
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
|
|
static int do_hardware_irq(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport *port = (struct parport *)table->extra1;
|
|
char buffer[20];
|
|
int len = 0;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (write) /* permissions prevent this anyway */
|
|
return -EACCES;
|
|
|
|
len += scnprintf (buffer, sizeof(buffer), "%d\n", port->irq);
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
|
|
static int do_hardware_dma(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport *port = (struct parport *)table->extra1;
|
|
char buffer[20];
|
|
int len = 0;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (write) /* permissions prevent this anyway */
|
|
return -EACCES;
|
|
|
|
len += scnprintf (buffer, sizeof(buffer), "%d\n", port->dma);
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
|
|
static int do_hardware_modes(struct ctl_table *table, int write,
|
|
void *result, size_t *lenp, loff_t *ppos)
|
|
{
|
|
struct parport *port = (struct parport *)table->extra1;
|
|
char buffer[40];
|
|
int len = 0;
|
|
|
|
if (*ppos) {
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (write) /* permissions prevent this anyway */
|
|
return -EACCES;
|
|
|
|
{
|
|
#define printmode(x) \
|
|
do { \
|
|
if (port->modes & PARPORT_MODE_##x) \
|
|
len += scnprintf(buffer + len, sizeof(buffer) - len, "%s%s", f++ ? "," : "", #x); \
|
|
} while (0)
|
|
int f = 0;
|
|
printmode(PCSPP);
|
|
printmode(TRISTATE);
|
|
printmode(COMPAT);
|
|
printmode(EPP);
|
|
printmode(ECP);
|
|
printmode(DMA);
|
|
#undef printmode
|
|
}
|
|
buffer[len++] = '\n';
|
|
|
|
if (len > *lenp)
|
|
len = *lenp;
|
|
else
|
|
*lenp = len;
|
|
|
|
*ppos += len;
|
|
memcpy(result, buffer, len);
|
|
return 0;
|
|
}
|
|
|
|
static const unsigned long parport_min_timeslice_value =
|
|
PARPORT_MIN_TIMESLICE_VALUE;
|
|
|
|
static const unsigned long parport_max_timeslice_value =
|
|
PARPORT_MAX_TIMESLICE_VALUE;
|
|
|
|
static const int parport_min_spintime_value =
|
|
PARPORT_MIN_SPINTIME_VALUE;
|
|
|
|
static const int parport_max_spintime_value =
|
|
PARPORT_MAX_SPINTIME_VALUE;
|
|
|
|
|
|
struct parport_sysctl_table {
|
|
struct ctl_table_header *port_header;
|
|
struct ctl_table_header *devices_header;
|
|
struct ctl_table vars[12];
|
|
struct ctl_table device_dir[2];
|
|
};
|
|
|
|
static const struct parport_sysctl_table parport_sysctl_template = {
|
|
.port_header = NULL,
|
|
.devices_header = NULL,
|
|
{
|
|
{
|
|
.procname = "spintime",
|
|
.data = NULL,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec_minmax,
|
|
.extra1 = (void*) &parport_min_spintime_value,
|
|
.extra2 = (void*) &parport_max_spintime_value
|
|
},
|
|
{
|
|
.procname = "base-addr",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_hardware_base_addr
|
|
},
|
|
{
|
|
.procname = "irq",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_hardware_irq
|
|
},
|
|
{
|
|
.procname = "dma",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_hardware_dma
|
|
},
|
|
{
|
|
.procname = "modes",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_hardware_modes
|
|
},
|
|
#ifdef CONFIG_PARPORT_1284
|
|
{
|
|
.procname = "autoprobe",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_autoprobe
|
|
},
|
|
{
|
|
.procname = "autoprobe0",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_autoprobe
|
|
},
|
|
{
|
|
.procname = "autoprobe1",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_autoprobe
|
|
},
|
|
{
|
|
.procname = "autoprobe2",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_autoprobe
|
|
},
|
|
{
|
|
.procname = "autoprobe3",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_autoprobe
|
|
},
|
|
#endif /* IEEE 1284 support */
|
|
{}
|
|
},
|
|
{
|
|
{
|
|
.procname = "active",
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0444,
|
|
.proc_handler = do_active_device
|
|
},
|
|
{}
|
|
},
|
|
};
|
|
|
|
struct parport_device_sysctl_table
|
|
{
|
|
struct ctl_table_header *sysctl_header;
|
|
struct ctl_table vars[2];
|
|
struct ctl_table device_dir[2];
|
|
struct ctl_table devices_root_dir[2];
|
|
struct ctl_table port_dir[2];
|
|
struct ctl_table parport_dir[2];
|
|
struct ctl_table dev_dir[2];
|
|
};
|
|
|
|
static const struct parport_device_sysctl_table
|
|
parport_device_sysctl_template = {
|
|
.sysctl_header = NULL,
|
|
{
|
|
{
|
|
.procname = "timeslice",
|
|
.data = NULL,
|
|
.maxlen = sizeof(unsigned long),
|
|
.mode = 0644,
|
|
.proc_handler = proc_doulongvec_ms_jiffies_minmax,
|
|
.extra1 = (void*) &parport_min_timeslice_value,
|
|
.extra2 = (void*) &parport_max_timeslice_value
|
|
},
|
|
{}
|
|
},
|
|
{
|
|
{
|
|
.procname = NULL,
|
|
.data = NULL,
|
|
.maxlen = 0,
|
|
.mode = 0555,
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
struct parport_default_sysctl_table
|
|
{
|
|
struct ctl_table_header *sysctl_header;
|
|
struct ctl_table vars[3];
|
|
struct ctl_table default_dir[2];
|
|
struct ctl_table parport_dir[2];
|
|
struct ctl_table dev_dir[2];
|
|
};
|
|
|
|
static struct parport_default_sysctl_table
|
|
parport_default_sysctl_table = {
|
|
.sysctl_header = NULL,
|
|
{
|
|
{
|
|
.procname = "timeslice",
|
|
.data = &parport_default_timeslice,
|
|
.maxlen = sizeof(parport_default_timeslice),
|
|
.mode = 0644,
|
|
.proc_handler = proc_doulongvec_ms_jiffies_minmax,
|
|
.extra1 = (void*) &parport_min_timeslice_value,
|
|
.extra2 = (void*) &parport_max_timeslice_value
|
|
},
|
|
{
|
|
.procname = "spintime",
|
|
.data = &parport_default_spintime,
|
|
.maxlen = sizeof(parport_default_spintime),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec_minmax,
|
|
.extra1 = (void*) &parport_min_spintime_value,
|
|
.extra2 = (void*) &parport_max_spintime_value
|
|
},
|
|
{}
|
|
}
|
|
};
|
|
|
|
int parport_proc_register(struct parport *port)
|
|
{
|
|
struct parport_sysctl_table *t;
|
|
char *tmp_dir_path;
|
|
size_t tmp_path_len, port_name_len;
|
|
int bytes_written, i, err = 0;
|
|
|
|
t = kmemdup(&parport_sysctl_template, sizeof(*t), GFP_KERNEL);
|
|
if (t == NULL)
|
|
return -ENOMEM;
|
|
|
|
t->device_dir[0].extra1 = port;
|
|
|
|
t->vars[0].data = &port->spintime;
|
|
for (i = 0; i < 5; i++) {
|
|
t->vars[i].extra1 = port;
|
|
t->vars[5 + i].extra2 = &port->probe_info[i];
|
|
}
|
|
|
|
port_name_len = strnlen(port->name, PARPORT_NAME_MAX_LEN);
|
|
/*
|
|
* Allocate a buffer for two paths: dev/parport/PORT and dev/parport/PORT/devices.
|
|
* We calculate for the second as that will give us enough for the first.
|
|
*/
|
|
tmp_path_len = PARPORT_BASE_DEVICES_PATH_SIZE + port_name_len;
|
|
tmp_dir_path = kzalloc(tmp_path_len, GFP_KERNEL);
|
|
if (!tmp_dir_path) {
|
|
err = -ENOMEM;
|
|
goto exit_free_t;
|
|
}
|
|
|
|
bytes_written = snprintf(tmp_dir_path, tmp_path_len,
|
|
"dev/parport/%s/devices", port->name);
|
|
if (tmp_path_len <= bytes_written) {
|
|
err = -ENOENT;
|
|
goto exit_free_tmp_dir_path;
|
|
}
|
|
t->devices_header = register_sysctl(tmp_dir_path, t->device_dir);
|
|
if (t->devices_header == NULL) {
|
|
err = -ENOENT;
|
|
goto exit_free_tmp_dir_path;
|
|
}
|
|
|
|
tmp_path_len = PARPORT_BASE_PATH_SIZE + port_name_len;
|
|
bytes_written = snprintf(tmp_dir_path, tmp_path_len,
|
|
"dev/parport/%s", port->name);
|
|
if (tmp_path_len <= bytes_written) {
|
|
err = -ENOENT;
|
|
goto unregister_devices_h;
|
|
}
|
|
|
|
t->port_header = register_sysctl(tmp_dir_path, t->vars);
|
|
if (t->port_header == NULL) {
|
|
err = -ENOENT;
|
|
goto unregister_devices_h;
|
|
}
|
|
|
|
port->sysctl_table = t;
|
|
|
|
kfree(tmp_dir_path);
|
|
return 0;
|
|
|
|
unregister_devices_h:
|
|
unregister_sysctl_table(t->devices_header);
|
|
|
|
exit_free_tmp_dir_path:
|
|
kfree(tmp_dir_path);
|
|
|
|
exit_free_t:
|
|
kfree(t);
|
|
return err;
|
|
}
|
|
|
|
int parport_proc_unregister(struct parport *port)
|
|
{
|
|
if (port->sysctl_table) {
|
|
struct parport_sysctl_table *t = port->sysctl_table;
|
|
port->sysctl_table = NULL;
|
|
unregister_sysctl_table(t->devices_header);
|
|
unregister_sysctl_table(t->port_header);
|
|
kfree(t);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int parport_device_proc_register(struct pardevice *device)
|
|
{
|
|
int bytes_written, err = 0;
|
|
struct parport_device_sysctl_table *t;
|
|
struct parport * port = device->port;
|
|
size_t port_name_len, device_name_len, tmp_dir_path_len;
|
|
char *tmp_dir_path;
|
|
|
|
t = kmemdup(&parport_device_sysctl_template, sizeof(*t), GFP_KERNEL);
|
|
if (t == NULL)
|
|
return -ENOMEM;
|
|
|
|
port_name_len = strnlen(port->name, PARPORT_NAME_MAX_LEN);
|
|
device_name_len = strnlen(device->name, PATH_MAX);
|
|
|
|
/* Allocate a buffer for two paths: dev/parport/PORT/devices/DEVICE. */
|
|
tmp_dir_path_len = PARPORT_BASE_DEVICES_PATH_SIZE + port_name_len + device_name_len;
|
|
tmp_dir_path = kzalloc(tmp_dir_path_len, GFP_KERNEL);
|
|
if (!tmp_dir_path) {
|
|
err = -ENOMEM;
|
|
goto exit_free_t;
|
|
}
|
|
|
|
bytes_written = snprintf(tmp_dir_path, tmp_dir_path_len, "dev/parport/%s/devices/%s",
|
|
port->name, device->name);
|
|
if (tmp_dir_path_len <= bytes_written) {
|
|
err = -ENOENT;
|
|
goto exit_free_path;
|
|
}
|
|
|
|
t->vars[0].data = &device->timeslice;
|
|
|
|
t->sysctl_header = register_sysctl(tmp_dir_path, t->vars);
|
|
if (t->sysctl_header == NULL) {
|
|
kfree(t);
|
|
t = NULL;
|
|
}
|
|
device->sysctl_table = t;
|
|
|
|
kfree(tmp_dir_path);
|
|
return 0;
|
|
|
|
exit_free_path:
|
|
kfree(tmp_dir_path);
|
|
|
|
exit_free_t:
|
|
kfree(t);
|
|
|
|
return err;
|
|
}
|
|
|
|
int parport_device_proc_unregister(struct pardevice *device)
|
|
{
|
|
if (device->sysctl_table) {
|
|
struct parport_device_sysctl_table *t = device->sysctl_table;
|
|
device->sysctl_table = NULL;
|
|
unregister_sysctl_table(t->sysctl_header);
|
|
kfree(t);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __init parport_default_proc_register(void)
|
|
{
|
|
int ret;
|
|
|
|
parport_default_sysctl_table.sysctl_header =
|
|
register_sysctl("dev/parport/default", parport_default_sysctl_table.vars);
|
|
if (!parport_default_sysctl_table.sysctl_header)
|
|
return -ENOMEM;
|
|
ret = parport_bus_init();
|
|
if (ret) {
|
|
unregister_sysctl_table(parport_default_sysctl_table.
|
|
sysctl_header);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __exit parport_default_proc_unregister(void)
|
|
{
|
|
if (parport_default_sysctl_table.sysctl_header) {
|
|
unregister_sysctl_table(parport_default_sysctl_table.
|
|
sysctl_header);
|
|
parport_default_sysctl_table.sysctl_header = NULL;
|
|
}
|
|
parport_bus_exit();
|
|
}
|
|
|
|
#else /* no sysctl or no procfs*/
|
|
|
|
int parport_proc_register(struct parport *pp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int parport_proc_unregister(struct parport *pp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int parport_device_proc_register(struct pardevice *device)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int parport_device_proc_unregister(struct pardevice *device)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int __init parport_default_proc_register (void)
|
|
{
|
|
return parport_bus_init();
|
|
}
|
|
|
|
static void __exit parport_default_proc_unregister (void)
|
|
{
|
|
parport_bus_exit();
|
|
}
|
|
#endif
|
|
|
|
subsys_initcall(parport_default_proc_register)
|
|
module_exit(parport_default_proc_unregister)
|