如何在内核 space 中分配用户 space 内存?
how to alloc user space memory in kernel space?
我在 Linux 上挂接了一个系统调用(打开),并想打印这个打开的文件名。
然后我调用 syscall(getcwd) 来获取绝对路径。
这是源代码:
void *memndup_from_user(const void __user *src, long len)
{
void *kbuf = NULL;
if(src == NULL) {
return kbuf;
}
kbuf = kmalloc(len + 1, GFP_KERNEL);
if(kbuf != NULL) {
if (copy_from_user(kbuf, src, len)) {
printk(KERN_ALERT "%s\n", "copy_from_user failed.");
kfree(kbuf);
kbuf = NULL;
}
else {
((char *)kbuf)[len] = '[=12=]';
}
} else {
printk(KERN_ALERT "%s\n", "kmalloc failed.");
}
return kbuf;
}
void *memdup_from_user(const void __user *src)
{
long len = 0;
if(src == NULL) {
return NULL;
}
len = strlen_user(src);
return memndup_from_user(src, len);
}
asmlinkage long fake_getcwd(char __user *buf, unsigned long size)
{
return real_getcwd(buf, size);
}
asmlinkage long
fake_open(const char __user *filename, int flags, umode_t mode)
{
if(flags & O_CREAT) {
char *k_filename = (char *)memdup_from_user(filename);
char *u_path = (char *)kmalloc(PAGE_SIZE, GFP_USER);
if(k_filename != NULL) {
printk(KERN_ALERT "ano_fake_open pid:%ld create : %s\n", ano_fake_getpid(), k_filename);
kfree(k_filename);
}
if(u_path != NULL) {
long retv;
retv = fake_getcwd(u_path, PAGE_SIZE);
if(retv > 0) {
printk(KERN_ALERT "getcwd ret val: %ld, path: %s\n", retv, u_path);
} else {
printk(KERN_ALERT "getcwd ret val: %ld, error...\n", retv);
}
kfree(u_path);
}
}
return real_open(filename, flags, mode);
}
sys_getcwd 需要用户 space 内存,我用 GFP_USER 调用 kmalloc。
但是 sys_getcwd 总是 return -EFAULT(错误地址)...
这是 dmesg 日志:
[344897.726061] fake_open pid:70393 create : sssssssssssssssss
[344897.726065] getcwd ret val: -14, error...
[344897.727431] fake_open pid:695 create : /var/lib/rsyslog/imjournal.state.tmp
[344897.727440] getcwd ret val: -14, error...
所以我在 sys_getcwd 中找到了工具,他找到了
# define __user __attribute__((noderef, address_space(1)))
# define __kernel __attribute__((address_space(0)))
#define __getname() kmem_cache_alloc(names_cachep, GFP_KERNEL)
SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
char *page = __getname();
get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
...
// char *cwd = page + xxx; (xxx < PAGE_SIZE)
// len = PAGE_SIZE + page - cwd;
...
if (len <= size) {
error = len;
if (copy_to_user(buf, cwd, len))
error = -EFAULT;
}
}
显然,getcwd 分配带有标志 GFP_KERNEL 的内存,然后从 (GFP_KERNEL) !!!
复制到我的缓冲区 ( __user *buf )
__user宏不是GFP_USER吗?
标志 GFP_USER 简介是 https://elixir.bootlin.com/linux/v4.4/source/include/linux/gfp.h#L208:
/* GFP_USER is for userspace allocations that also need to be directly
* accessibly by the kernel or hardware. It is typically used by hardware
* for buffers that are mapped to userspace (e.g. graphics) that hardware
* still must DMA to. cpuset limits are enforced for these allocations.
*/
怎么了?
至少有两个帐户是错误的:
- 系统调用劫持(更不用说 open 之类的东西了)只是个坏主意。捕获所有可能的开放路径的唯一明智方法是使用 LSM 挂钩。它也恰好处理正在打开的实际文件,避免了竞争:你在你的例程中读取路径,包装打开再次读取它。但到那时恶意用户空间可能已经更改了它,你最终看到了错误的文件。
- 应该清楚 getcwd 必须有一个解析名称的方法才能将其放入用户空间缓冲区。您应该深入研究调用,看看可以更改哪些内容以将其放入内核缓冲区。
你为什么要开始这样做?
我在 Linux 上挂接了一个系统调用(打开),并想打印这个打开的文件名。 然后我调用 syscall(getcwd) 来获取绝对路径。
这是源代码:
void *memndup_from_user(const void __user *src, long len)
{
void *kbuf = NULL;
if(src == NULL) {
return kbuf;
}
kbuf = kmalloc(len + 1, GFP_KERNEL);
if(kbuf != NULL) {
if (copy_from_user(kbuf, src, len)) {
printk(KERN_ALERT "%s\n", "copy_from_user failed.");
kfree(kbuf);
kbuf = NULL;
}
else {
((char *)kbuf)[len] = '[=12=]';
}
} else {
printk(KERN_ALERT "%s\n", "kmalloc failed.");
}
return kbuf;
}
void *memdup_from_user(const void __user *src)
{
long len = 0;
if(src == NULL) {
return NULL;
}
len = strlen_user(src);
return memndup_from_user(src, len);
}
asmlinkage long fake_getcwd(char __user *buf, unsigned long size)
{
return real_getcwd(buf, size);
}
asmlinkage long
fake_open(const char __user *filename, int flags, umode_t mode)
{
if(flags & O_CREAT) {
char *k_filename = (char *)memdup_from_user(filename);
char *u_path = (char *)kmalloc(PAGE_SIZE, GFP_USER);
if(k_filename != NULL) {
printk(KERN_ALERT "ano_fake_open pid:%ld create : %s\n", ano_fake_getpid(), k_filename);
kfree(k_filename);
}
if(u_path != NULL) {
long retv;
retv = fake_getcwd(u_path, PAGE_SIZE);
if(retv > 0) {
printk(KERN_ALERT "getcwd ret val: %ld, path: %s\n", retv, u_path);
} else {
printk(KERN_ALERT "getcwd ret val: %ld, error...\n", retv);
}
kfree(u_path);
}
}
return real_open(filename, flags, mode);
}
sys_getcwd 需要用户 space 内存,我用 GFP_USER 调用 kmalloc。 但是 sys_getcwd 总是 return -EFAULT(错误地址)...
这是 dmesg 日志:
[344897.726061] fake_open pid:70393 create : sssssssssssssssss
[344897.726065] getcwd ret val: -14, error...
[344897.727431] fake_open pid:695 create : /var/lib/rsyslog/imjournal.state.tmp
[344897.727440] getcwd ret val: -14, error...
所以我在 sys_getcwd 中找到了工具,他找到了
# define __user __attribute__((noderef, address_space(1)))
# define __kernel __attribute__((address_space(0)))
#define __getname() kmem_cache_alloc(names_cachep, GFP_KERNEL)
SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
char *page = __getname();
get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
...
// char *cwd = page + xxx; (xxx < PAGE_SIZE)
// len = PAGE_SIZE + page - cwd;
...
if (len <= size) {
error = len;
if (copy_to_user(buf, cwd, len))
error = -EFAULT;
}
}
显然,getcwd 分配带有标志 GFP_KERNEL 的内存,然后从 (GFP_KERNEL) !!!
复制到我的缓冲区 ( __user *buf )__user宏不是GFP_USER吗?
标志 GFP_USER 简介是 https://elixir.bootlin.com/linux/v4.4/source/include/linux/gfp.h#L208:
/* GFP_USER is for userspace allocations that also need to be directly
* accessibly by the kernel or hardware. It is typically used by hardware
* for buffers that are mapped to userspace (e.g. graphics) that hardware
* still must DMA to. cpuset limits are enforced for these allocations.
*/
怎么了?
至少有两个帐户是错误的:
- 系统调用劫持(更不用说 open 之类的东西了)只是个坏主意。捕获所有可能的开放路径的唯一明智方法是使用 LSM 挂钩。它也恰好处理正在打开的实际文件,避免了竞争:你在你的例程中读取路径,包装打开再次读取它。但到那时恶意用户空间可能已经更改了它,你最终看到了错误的文件。
- 应该清楚 getcwd 必须有一个解析名称的方法才能将其放入用户空间缓冲区。您应该深入研究调用,看看可以更改哪些内容以将其放入内核缓冲区。
你为什么要开始这样做?