Linux内核中的sscanf函数是否容易受到缓冲区溢出攻击?

4
据我所了解,典型的缓冲区溢出攻击发生在攻击者将一个栈上的内存缓冲区溢出时,从而允许攻击者注入恶意代码并重写栈上的返回地址以指向该代码。
当使用函数(例如sscanf)盲目地从一个区域复制数据到另一个区域,检查其中一个终止字节时,这是一个常见的问题。
char str[8];                               /* holds up to 8 bytes of data */
char *buf = "lots and lots of foobars";    /* way more than 8 bytes of data */
sscanf(buf, "%s", str);                    /* buffer overflow occurs here! */

我注意到Linux内核中一些与sysfs_ops store相关的函数是使用Linux内核版本的sscanf函数实现的:

static char str[8];    /* global string */
static ssize_t my_store(struct device *dev,
                        struct device_attribute *attr,
                        const char *buf, size_t size)
{
        sscanf(buf, "%s", str);    /* buf holds more than 8 bytes! */
        return size;
}

假设将此store回调函数设置为可写的sysfs属性。那么恶意用户能否通过write调用有意地溢出缓冲区呢?
通常,我期望防止缓冲区溢出攻击的保护措施,例如限制读取的字节数,但是我在许多函数中都看不到这些措施(例如在drivers/scsi/scsi_sysfs.c中)。
Linux内核版本的sscanf实现是否防止缓冲区溢出攻击?或者另有原因 - 也许在Linux内核工作原理下缓冲区溢出攻击是不可能的?

2
软件安全意味着了解你所保护的内容和你要防范的对象。你担心什么?一个可加载内核模块可能会在内核中的其他位置导致缓冲区溢出?一个恶意设备吗?sscanf()本身并不是不安全的,只有当攻击者可以传递给它错误的参数时才会出现不安全情况。 - Pascal Cuoq
1
@PascalCuoq 请原谅我,我对 sysfs 还不是很熟悉。如果一个属性是可写的,那么恶意用户是否可以通过向属性文件进行 write 调用来故意溢出缓冲区呢? - Vilhelm Gray
1
我也不了解sysfs,但我认为你刚才写的评论让你的问题更好了。 - Pascal Cuoq
+1:比可移植性问题好多了。 - artless noise
2个回答

3
Linux中的sscanf()存在缓冲区溢出漏洞;检查代码显示。您可以使用宽度说明符限制%s允许写入的数量。在某些情况下,您的str必须也运行了copy_from_user()。用户空间可能会将一些垃圾指针传递给内核。
在您提到的Linux版本中,scsi_sysfs.c存在缓冲区溢出。最新版本不存在此问题。提交的修复程序应该解决您看到的问题。

1
用户空间如何向内核传递无效指针? - Vilhelm Gray
1
算了,我意识到字符缓冲指针是通过系统调用(如readwrite)传递的 - 因此用户理论上可以传递无效指针,例如指向内核空间地址的指针。 - Vilhelm Gray
дёәд»Җд№Ҳsysfs_opsз»“жһ„дёӯзҡ„еҮҪж•°жҢҮй’ҲжІЎжңүе°Ҷ__userе®Ҹеә”з”ЁдәҺзј“еҶІеҢәжҢҮй’ҲпјҹдҫӢеҰӮпјҢдёәд»Җд№ҲдёҚжҳҜпјҡssize_t (*show)(struct kobject *, struct attribute *,char __user *); - Vilhelm Gray
__usercompiler.h 中被定义。你可以看到,除非定义了 **CHECKER**,否则它不起作用。使用 make C=1 将使用 __user 注释。请参阅Sparse wiki。因此,在编译代码方面,它实际上什么也没做。这只是意味着如果你正确并且它们确实来自用户空间,那么它们将不会被 checker 检查。抱歉,我没有检查 ;-) - artless noise
我需要收回之前说的话;我不认为sysfs_ops函数的缓冲区位于用户空间。当我尝试使用copy_to_user/copy_from_user与缓冲区时,会出现“坏地址”错误。也许sysfs正在执行一些魔法,为数据分配内核空间缓冲区。 - Vilhelm Gray
1
抱歉,我有点混淆了。我不知道sysfs是否使用用户空间缓冲区。我只是一般地指当使用sscanf()时。例如从一个ioctl()或其他什么地方(以Pascal的评论为精神); sysfs是一个文件系统,数据可能已经在到达回调函数时从用户空间传输过来了。 - artless noise

0
短回答:
正确使用 sscanf 不会导致缓冲区溢出,尤其是在 sysfs 的 XXX_store() 函数中。在这里,(sysfs 的 XXX_store() 示例中有很多 sscanf 函数),因为 Linux 内核会在字符串后添加一个 '\0'(零终止)字节(buf[len] = 0;),用于您的 XXX_store() 函数。
长回答:
通常,sysfs 被定义为具有严格格式化数据。由于您期望最多8个字节,因此限制您获取的大小是合理的。
static char str[8];    /* global string */
static ssize_t my_store(struct device *dev,
                        struct device_attribute *attr,
                        const char *buf, size_t size)
{
        if (size > 8) {
                printk("Error: Input size > 8: too large\n");
                return -EINVAL;
        }
        sscanf(buf, "%s", str);    /* buf holds more than 8 bytes! */
        return size;
}

(备注:如果您期望一个8字节的字符串加上'\n',请使用9而不是8)

(请注意,您会拒绝一些输入,例如那些有许多前导空格的输入。然而,谁会发送一个有很多前导空格的字符串呢?那些想要破坏你代码的人吧?如果他们不遵循您的规范,只需拒绝他们。)

请注意,在用户向sysfs写入len字节以便安全地使用sscanf时,Linux内核故意在偏移量len处插入'\0'(即buf[len] = 0;),如2.6内核中的注释所述:fs/sysfs/file.c

static int 
fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t count)
{
    int error;

    if (!buffer->page)
        buffer->page = (char *)get_zeroed_page(GFP_KERNEL);
    if (!buffer->page)
        return -ENOMEM;

    if (count >= PAGE_SIZE)
        count = PAGE_SIZE - 1;
    error = copy_from_user(buffer->page,buf,count);
    buffer->needs_read_fill = 1;
    /* if buf is assumed to contain a string, terminate it by \0,
       so e.g. sscanf() can scan the string easily */
    buffer->page[count] = 0;
    return error ? -EFAULT : count;
}
...
static ssize_t
sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    struct sysfs_buffer * buffer = file->private_data;
    ssize_t len;

    mutex_lock(&buffer->mutex);
    len = fill_write_buffer(buffer, buf, count);
    if (len > 0)
        len = flush_write_buffer(file->f_path.dentry, buffer, len);
    if (len > 0)
        *ppos += len;
    mutex_unlock(&buffer->mutex);
    return len;
}

更高的内核版本保持相同的逻辑(尽管已经完全重写)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接