Commit afc11cbc authored by Alexander Viro's avatar Alexander Viro Committed by Linus Torvalds

[PATCH] sparse: ide-proc.c fixes

Fixed dereferencing userland pointers, general idiocy in parsing.
parent 70c7987d
...@@ -74,207 +74,210 @@ ...@@ -74,207 +74,210 @@
#include <asm/io.h> #include <asm/io.h>
static int ide_getxdigit(char c) static int proc_ide_write_config(struct file *file, const char __user *buffer,
{ unsigned long count, void *data)
int digit;
if (isdigit(c))
digit = c - '0';
else if (isxdigit(c))
digit = tolower(c) - 'a' + 10;
else
digit = -1;
return digit;
}
static int xx_xx_parse_error (const char *data, unsigned long len, const char *msg)
{
char errbuf[16];
int i;
if (len >= sizeof(errbuf))
len = sizeof(errbuf) - 1;
for (i = 0; i < len; ++i) {
char c = data[i];
if (!c || c == '\n')
c = '\0';
else if (iscntrl(c))
c = '?';
errbuf[i] = c;
}
errbuf[i] = '\0';
printk("proc_ide: error: %s: '%s'\n", msg, errbuf);
return -EINVAL;
}
static int proc_ide_write_config
(struct file *file, const char *buffer, unsigned long count, void *data)
{ {
ide_hwif_t *hwif = (ide_hwif_t *)data; ide_hwif_t *hwif = (ide_hwif_t *)data;
int for_real = 0; ide_hwgroup_t *mygroup = (ide_hwgroup_t *)(hwif->hwgroup);
unsigned long startn = 0, n, flags; ide_hwgroup_t *mategroup = NULL;
const char *start = NULL, *msg = NULL; unsigned long timeout;
unsigned long flags;
const char *start = NULL, *msg = NULL;
struct entry { u32 val; u16 reg; u8 size; u8 pci; } *prog, *q, *r;
int want_pci = 0;
char *buf, *s;
int err;
if (hwif->mate && hwif->mate->hwgroup)
mategroup = (ide_hwgroup_t *)(hwif->mate->hwgroup);
if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO)) if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
return -EACCES; return -EACCES;
/*
* Skip over leading whitespace if (count >= PAGE_SIZE)
*/ return -EINVAL;
while (count && isspace(*buffer)) {
--count; s = buf = (char *)__get_free_page(GFP_USER);
++buffer; if (!buf)
return -ENOMEM;
err = -ENOMEM;
q = prog = (struct entry *)__get_free_page(GFP_USER);
if (!prog)
goto out;
err = -EFAULT;
if (copy_from_user(buf, buffer, count))
goto out1;
buf[count] = '\0';
while (isspace(*s))
s++;
while (*s) {
char *p;
int digits;
start = s;
if ((char *)(q + 1) > (char *)prog + PAGE_SIZE) {
msg = "too many entries";
goto parse_error;
}
switch (*s++) {
case 'R': q->pci = 0;
break;
case 'P': q->pci = 1;
want_pci = 1;
break;
default: msg = "expected 'R' or 'P'";
goto parse_error;
}
q->reg = simple_strtoul(s, &p, 16);
digits = p - s;
if (!digits || digits > 4 || (q->pci && q->reg > 0xff)) {
msg = "bad/missing register number";
goto parse_error;
}
if (*p++ != ':') {
msg = "missing ':'";
goto parse_error;
}
q->val = simple_strtoul(p, &s, 16);
digits = s - p;
if (digits != 2 && digits != 4 && digits != 8) {
msg = "bad data, 2/4/8 digits required";
goto parse_error;
}
q->size = digits / 2;
if (q->pci) {
#ifdef CONFIG_BLK_DEV_IDEPCI
if (q->reg & (q->size - 1)) {
msg = "misaligned access";
goto parse_error;
}
#else
msg = "not a PCI device";
goto parse_error;
#endif /* CONFIG_BLK_DEV_IDEPCI */
}
q++;
if (*s && !isspace(*s++)) {
msg = "expected whitespace after data";
goto parse_error;
}
while (isspace(*s))
s++;
} }
/* /*
* Do one full pass to verify all parameters, * What follows below is fucking insane, even for IDE people.
* then do another to actually write the regs. * For now I've dealt with the obvious problems on the parsing
* side, but IMNSHO we should simply remove the write access
* to /proc/ide/.../config, killing that FPOS completely.
*/ */
err = -EBUSY;
timeout = jiffies + (3 * HZ);
spin_lock_irqsave(&ide_lock, flags); spin_lock_irqsave(&ide_lock, flags);
do { while (mygroup->busy ||
const char *p; (mategroup && mategroup->busy)) {
if (for_real) { spin_unlock_irqrestore(&ide_lock, flags);
unsigned long timeout = jiffies + (3 * HZ); if (time_after(jiffies, timeout)) {
ide_hwgroup_t *mygroup = (ide_hwgroup_t *)(hwif->hwgroup); printk("/proc/ide/%s/config: channel(s) busy, cannot write\n", hwif->name);
ide_hwgroup_t *mategroup = NULL; goto out1;
if (hwif->mate && hwif->mate->hwgroup)
mategroup = (ide_hwgroup_t *)(hwif->mate->hwgroup);
spin_lock_irqsave(&ide_lock, flags);
while (mygroup->busy ||
(mategroup && mategroup->busy)) {
spin_unlock_irqrestore(&ide_lock, flags);
if (time_after(jiffies, timeout)) {
printk("/proc/ide/%s/config: channel(s) busy, cannot write\n", hwif->name);
spin_unlock_irqrestore(&ide_lock, flags);
return -EBUSY;
}
spin_lock_irqsave(&ide_lock, flags);
}
} }
p = buffer; spin_lock_irqsave(&ide_lock, flags);
n = count; }
while (n > 0) {
int d, digits;
unsigned int reg = 0, val = 0, is_pci;
start = p;
startn = n--;
switch (*p++) {
case 'R': is_pci = 0;
break;
case 'P': is_pci = 1;
#ifdef CONFIG_BLK_DEV_IDEPCI #ifdef CONFIG_BLK_DEV_IDEPCI
if (hwif->pci_dev && !hwif->pci_dev->vendor) if (want_pci && (!hwif->pci_dev || hwif->pci_dev->vendor)) {
break; spin_unlock_irqrestore(&ide_lock, flags);
printk("proc_ide: PCI registers not accessible for %s\n",
hwif->name);
err = -EINVAL;
goto out1;
}
#endif /* CONFIG_BLK_DEV_IDEPCI */ #endif /* CONFIG_BLK_DEV_IDEPCI */
msg = "not a PCI device";
goto parse_error; for (r = prog; r < q; r++) {
default: msg = "expected 'R' or 'P'"; unsigned int reg = r->reg, val = r->val;
goto parse_error; if (r->pci) {
}
digits = 0;
while (n > 0 && (d = ide_getxdigit(*p)) >= 0) {
reg = (reg << 4) | d;
--n;
++p;
++digits;
}
if (!digits || (digits > 4) || (is_pci && reg > 0xff)) {
msg = "bad/missing register number";
goto parse_error;
}
if (n-- == 0 || *p++ != ':') {
msg = "missing ':'";
goto parse_error;
}
digits = 0;
while (n > 0 && (d = ide_getxdigit(*p)) >= 0) {
val = (val << 4) | d;
--n;
++p;
++digits;
}
if (digits != 2 && digits != 4 && digits != 8) {
msg = "bad data, 2/4/8 digits required";
goto parse_error;
}
if (n > 0 && !isspace(*p)) {
msg = "expected whitespace after data";
goto parse_error;
}
while (n > 0 && isspace(*p)) {
--n;
++p;
}
#ifdef CONFIG_BLK_DEV_IDEPCI #ifdef CONFIG_BLK_DEV_IDEPCI
if (is_pci && (reg & ((digits >> 1) - 1))) { int rc = 0;
msg = "misaligned access"; struct pci_dev *dev = hwif->pci_dev;
goto parse_error; switch (q->size) {
case 1: msg = "byte";
rc = pci_write_config_byte(dev, reg, val);
break;
case 2: msg = "word";
rc = pci_write_config_word(dev, reg, val);
break;
case 4: msg = "dword";
rc = pci_write_config_dword(dev, reg, val);
break;
}
if (rc) {
spin_unlock_irqrestore(&ide_lock, flags);
printk("proc_ide_write_config: error writing %s at bus %02x dev %02x reg 0x%x value 0x%x\n",
msg, dev->bus->number, dev->devfn, reg, val);
printk("proc_ide_write_config: error %d\n", rc);
err = -EIO;
goto out1;
} }
#endif /* CONFIG_BLK_DEV_IDEPCI */ #endif /* CONFIG_BLK_DEV_IDEPCI */
if (for_real) { } else { /* not pci */
#if 0
printk("proc_ide_write_config: type=%c, reg=0x%x, val=0x%x, digits=%d\n", is_pci ? "PCI" : "non-PCI", reg, val, digits);
#endif
if (is_pci) {
#ifdef CONFIG_BLK_DEV_IDEPCI
int rc = 0;
struct pci_dev *dev = hwif->pci_dev;
switch (digits) {
case 2: msg = "byte";
rc = pci_write_config_byte(dev, reg, val);
break;
case 4: msg = "word";
rc = pci_write_config_word(dev, reg, val);
break;
case 8: msg = "dword";
rc = pci_write_config_dword(dev, reg, val);
break;
}
if (rc) {
spin_unlock_irqrestore(&ide_lock, flags);
printk("proc_ide_write_config: error writing %s at bus %02x dev %02x reg 0x%x value 0x%x\n",
msg, dev->bus->number, dev->devfn, reg, val);
printk("proc_ide_write_config: error %d\n", rc);
return -EIO;
}
#endif /* CONFIG_BLK_DEV_IDEPCI */
} else { /* not pci */
#if !defined(__mc68000__) && !defined(CONFIG_APUS) #if !defined(__mc68000__) && !defined(CONFIG_APUS)
/* /*
* Geert Uytterhoeven * Geert Uytterhoeven
* *
* unless you can explain me what it really does. * unless you can explain me what it really does.
* On m68k, we don't have outw() and outl() yet, * On m68k, we don't have outw() and outl() yet,
* and I need a good reason to implement it. * and I need a good reason to implement it.
* *
* BTW, IMHO the main remaining portability problem with the IDE driver * BTW, IMHO the main remaining portability problem with the IDE driver
* is that it mixes IO (ioport) and MMIO (iomem) access on different platforms. * is that it mixes IO (ioport) and MMIO (iomem) access on different platforms.
* *
* I think all accesses should be done using * I think all accesses should be done using
* *
* ide_in[bwl](ide_device_instance, offset) * ide_in[bwl](ide_device_instance, offset)
* ide_out[bwl](ide_device_instance, value, offset) * ide_out[bwl](ide_device_instance, value, offset)
* *
* so the architecture specific code can #define ide_{in,out}[bwl] to the * so the architecture specific code can #define ide_{in,out}[bwl] to the
* appropriate function. * appropriate function.
* *
*/ */
switch (digits) { switch (r->size) {
case 2: hwif->OUTB(val, reg); case 1: hwif->OUTB(val, reg);
break; break;
case 4: hwif->OUTW(val, reg); case 2: hwif->OUTW(val, reg);
break; break;
case 8: hwif->OUTL(val, reg); case 4: hwif->OUTL(val, reg);
break; break;
}
#endif /* !__mc68000__ && !CONFIG_APUS */
}
} }
#endif /* !__mc68000__ && !CONFIG_APUS */
} }
} while (!for_real++); }
spin_unlock_irqrestore(&ide_lock, flags); spin_unlock_irqrestore(&ide_lock, flags);
return count; err = count;
out1:
free_page((unsigned long)prog);
out:
free_page((unsigned long)buf);
return err;
parse_error: parse_error:
spin_unlock_irqrestore(&ide_lock, flags);
printk("parse error\n"); printk("parse error\n");
return xx_xx_parse_error(start, startn, msg); printk("proc_ide: error: %s: '%s'\n", msg, start);
err = -EINVAL;
goto out1;
} }
int proc_ide_read_config int proc_ide_read_config
...@@ -316,16 +319,6 @@ int proc_ide_read_config ...@@ -316,16 +319,6 @@ int proc_ide_read_config
EXPORT_SYMBOL(proc_ide_read_config); EXPORT_SYMBOL(proc_ide_read_config);
static int ide_getdigit(char c)
{
int digit;
if (isdigit(c))
digit = c - '0';
else
digit = -1;
return digit;
}
static int proc_ide_read_imodel static int proc_ide_read_imodel
(char *page, char **start, off_t off, int count, int *eof, void *data) (char *page, char **start, off_t off, int count, int *eof, void *data)
{ {
...@@ -470,37 +463,50 @@ EXPORT_SYMBOL(proc_ide_read_settings); ...@@ -470,37 +463,50 @@ EXPORT_SYMBOL(proc_ide_read_settings);
#define MAX_LEN 30 #define MAX_LEN 30
int proc_ide_write_settings int proc_ide_write_settings(struct file *file, const char __user *buffer,
(struct file *file, const char *buffer, unsigned long count, void *data) unsigned long count, void *data)
{ {
ide_drive_t *drive = (ide_drive_t *) data; ide_drive_t *drive = (ide_drive_t *) data;
char name[MAX_LEN + 1]; char name[MAX_LEN + 1];
int for_real = 0, len; int for_real = 0;
unsigned long n; unsigned long n;
const char *start = NULL;
ide_settings_t *setting; ide_settings_t *setting;
char *buf, *s;
if (!capable(CAP_SYS_ADMIN)) if (!capable(CAP_SYS_ADMIN))
return -EACCES; return -EACCES;
if (count >= PAGE_SIZE)
return -EINVAL;
s = buf = (char *)__get_free_page(GFP_USER);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, buffer, count)) {
free_page((unsigned long)buf);
return -EFAULT;
}
buf[count] = '\0';
/* /*
* Skip over leading whitespace * Skip over leading whitespace
*/ */
while (count && isspace(*buffer)) { while (count && isspace(*s)) {
--count; --count;
++buffer; ++s;
} }
/* /*
* Do one full pass to verify all parameters, * Do one full pass to verify all parameters,
* then do another to actually write the new settings. * then do another to actually write the new settings.
*/ */
do { do {
const char *p; char *p = s;
p = buffer;
n = count; n = count;
while (n > 0) { while (n > 0) {
int d, digits; unsigned val;
unsigned int val = 0; char *q = p;
start = p;
while (n > 0 && *p != ':') { while (n > 0 && *p != ':') {
--n; --n;
...@@ -508,30 +514,27 @@ int proc_ide_write_settings ...@@ -508,30 +514,27 @@ int proc_ide_write_settings
} }
if (*p != ':') if (*p != ':')
goto parse_error; goto parse_error;
len = min_t(int, p - start, MAX_LEN); if (p - q > MAX_LEN)
strncpy(name, start, min(len, MAX_LEN)); goto parse_error;
name[len] = 0; memcpy(name, q, p - q);
name[p - q] = 0;
if (n > 0) { if (n > 0) {
--n; --n;
p++; p++;
} else } else
goto parse_error; goto parse_error;
digits = 0; val = simple_strtoul(p, &q, 10);
while (n > 0 && (d = ide_getdigit(*p)) >= 0) { n -= q - p;
val = (val * 10) + d; p = q;
--n;
++p;
++digits;
}
if (n > 0 && !isspace(*p)) if (n > 0 && !isspace(*p))
goto parse_error; goto parse_error;
while (n > 0 && isspace(*p)) { while (n > 0 && isspace(*p)) {
--n; --n;
++p; ++p;
} }
down(&ide_setting_sem); down(&ide_setting_sem);
setting = ide_find_setting_by_name(drive, name); setting = ide_find_setting_by_name(drive, name);
if (!setting) if (!setting)
...@@ -544,8 +547,10 @@ int proc_ide_write_settings ...@@ -544,8 +547,10 @@ int proc_ide_write_settings
up(&ide_setting_sem); up(&ide_setting_sem);
} }
} while (!for_real++); } while (!for_real++);
free_page((unsigned long)buf);
return count; return count;
parse_error: parse_error:
free_page((unsigned long)buf);
printk("proc_ide_write_settings(): parse error\n"); printk("proc_ide_write_settings(): parse error\n");
return -EINVAL; return -EINVAL;
} }
...@@ -612,13 +617,19 @@ int proc_ide_read_driver ...@@ -612,13 +617,19 @@ int proc_ide_read_driver
EXPORT_SYMBOL(proc_ide_read_driver); EXPORT_SYMBOL(proc_ide_read_driver);
int proc_ide_write_driver int proc_ide_write_driver
(struct file *file, const char *buffer, unsigned long count, void *data) (struct file *file, const char __user *buffer, unsigned long count, void *data)
{ {
ide_drive_t *drive = (ide_drive_t *) data; ide_drive_t *drive = (ide_drive_t *) data;
char name[32];
if (!capable(CAP_SYS_ADMIN)) if (!capable(CAP_SYS_ADMIN))
return -EACCES; return -EACCES;
if (ide_replace_subdriver(drive, buffer)) if (count > 31)
count = 31;
if (copy_from_user(name, buffer, count))
return -EFAULT;
name[count] = '\0';
if (ide_replace_subdriver(drive, name))
return -EINVAL; return -EINVAL;
return count; return count;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment