Linux Kernel中获取当前目录方法(undone)
目录
0. 引言 1. 基于进程内存镜像信息struct mm_struct获取struct path调用d_path()获取当前进程的"绝对路径" 2. 基于文件描述符(fd)、task_struct调用d_path()获取当前进程所打开文件的"绝对路径" 3. 基于dentry、vfsmount调用d_path()获取当前进程的"当前目录" 4. 基于jprobe监控load_module()系统调用获取当前正在加载的LKM文件的绝对路径 5. 基于get_fs_pwd获取当前目录
0. 引言
本文涉及的是ring0下的获取当前进程工作目录的方法,LKM位于linux的内核内存区域,任何进程都可以通过LKM的导出函数指定当前LKM的代码,所以,我们需要在LKM中获取当前调用进程的当前工作目录
1. 基于进程内存镜像信息struct mm_struct获取struct path调用d_path()获取当前进程的"绝对路径"
d_path是内核提供的根据dentry和vfsmount获取绝对路径函数
此函数有2个版本,以内核版本2.6.25为分界 1. extern char *d_path(const struct path *, char *, int); 2. extern char * d_path(struct dentry *, struct vfsmount *, char *, int);
get_absolute_path.c
#include <linux/module.h> #include <linux/fcntl.h>//for O_RDONLY #include <linux/fs.h>//for filp_open #include <linux/uaccess.h>//for get_fs #include <linux/limits.h>//for PATH_MAX #include <linux/sched.h> #include <linux/mm.h> char* get_absolute_path(struct task_struct * task) { char * ret_ptr = NULL; char * tpath = NULL ; struct vm_area_struct * vma = NULL; struct path base_path; tpath = (char*)kmalloc(512, 0); if(NULL == tpath || NULL == task) { return NULL; } memset(tpath,'\0',512); task_lock(task); /* 获取当前进程的内存空间信息(通过内存空间) */ if(task->mm && task->mm->mmap) { vma = task->mm->mmap; } else { task_unlock(task); kfree(tpath); return NULL; } /* 取得path(a struct含dentry和vfsmount) */ while(vma) { if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { base_path = vma->vm_file->f_path; break; } vma = vma->vm_next; } task_unlock(task); /* * 调用 d_path, 得到绝对路径 */ ret_ptr = d_path(&base_path, tpath, 512); return ret_ptr; } int init_module(void) { struct task_struct * task = current; char *path = get_absolute_path(task); printk("FULLPATH: %s\n", path); return 0; } void cleanup_module(void) { } MODULE_LICENSE("GPL");
Makefile
# # Variables needed to build the kernel module # name = get_absolute_path obj-m += $(name).o all: build .PHONY: build install clean build: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules CONFIG_DEBUG_SECTION_MISMATCH=y install: build -mkdir -p /lib/modules/`uname -r`/kernel/arch/x86/kernel/ cp $(name).ko /lib/modules/`uname -r`/kernel/arch/x86/kernel/ depmod /lib/modules/`uname -r`/kernel/arch/x86/kernel/$(name).ko clean: [ -d /lib/modules/$(shell uname -r)/build ] && \ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Relevant Link:
http://blog.csdn.net/cenziboy/article/details/8761621
2. 基于文件描述符(fd)、task_struct调用d_path()获取当前进程所打开文件的"绝对路径"
#include <linux/fdtable.h>
/* 根据task_struct、fd(文件描述符)获取当前工作路径 */ int get_path(struct task_struct *mytask, int fd) { struct file *myfile = NULL; struct files_struct *files = NULL; char path[100] = {'\0'}; char *ppath = path; files = mytask->files; if (!files) { printk("files is null..\n"); return -1; } myfile = files->fdt->fd[fd]; if (!myfile) { printk("myfile is null..\n"); return -1; } ppath = d_path(&(myfile->f_path), ppath, 100); printk("FULLPATH: %s\n", ppath); return 1; }
Relevant Link:
http://edsionte.com/techblog/archives/4406 http://xd03071149.blog.163.com/blog/static/12350636320129822841854/
3. 基于dentry、vfsmount调用d_path()获取当前进程的"当前目录"
1. 首先得到文件对应的struct file结构 2. 后再用结构中的f_dentry和f_vfsmnt字段充当d_path的前两个参数 3. 得到了这个文件的绝对路径了具体步骤如下
关于linux内核中和文件系统、VFS相关的数据结构,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html
code
#include <linux/module.h> #include <linux/fcntl.h>//for O_RDONLY #include <linux/fs.h>//for filp_open #include <linux/uaccess.h>//for get_fs #include <linux/limits.h>//for PATH_MAX #include <linux/sched.h> #include <linux/mm.h> #include <linux/fdtable.h> #include <linux/types.h> #include <linux/stat.h> #include <linux/unistd.h> #include <linux/dcache.h> #include <linux/fs_struct.h> #include <linux/mount.h> #define MAX_TMPPATH 1024 void get_absolute_path(char *mod_name) { /* 假设 1024 已经足够了 */ char tmp_path[MAX_TMPPATH] = {0}; char * ptr = NULL; struct dentry *dentry = NULL; struct vfsmount * mnt = NULL; memset(tmp_path, 0, MAX_TMPPATH); ptr = tmp_path + MAX_TMPPATH - 256; task_lock(current); /* Get the Process working dentry */ dentry = current->fs->pwd.dentry; /* Get the Process working vfsmount */ mnt = current->fs->pwd.mnt; do { /* Process the dentry */ while(dentry && dentry->d_name.name && strcmp(dentry->d_name.name,"/")) { ptr = ptr - strlen(dentry->d_name.name); if(ptr <= tmp_path + strlen(dentry->d_name.name) + 1) { break; } memcpy(ptr, dentry->d_name.name, strlen(dentry->d_name.name)); *(--ptr) = '/'; dentry= dentry->d_parent; } /* Process the filesystem mountpoint, 突破挂载点 */ if(mnt && mnt->mnt_mountpoint && mnt->mnt_mountpoint->d_name.name && strcmp(mnt->mnt_mountpoint->d_name.name,"/")) { dentry = mnt->mnt_mountpoint; ptr = ptr - strlen(dentry->d_name.name); if(ptr <= tmp_path + strlen(dentry->d_name.name) + 1) { break; } memcpy(ptr,dentry->d_name.name,strlen(dentry->d_name.name)); *(--ptr) = '/'; mnt = mnt->mnt_parent; dentry= dentry->d_parent; } } while( 0 != strcmp(mnt->mnt_mountpoint->d_name.name,"/")); /* end do */ /* 直到文件系统的挂载点为 / */ task_unlock(current); //concat the full path strcat(ptr, "/"); strcat(ptr, mod_name); strcat(ptr, ".ko"); printk("full path: %s\n",ptr); } int init_module(void) { get_absolute_path(THIS_MODULE->name); } void cleanup_module(void) { } MODULE_LICENSE("GPL");
Makefile
# # Variables needed to build the kernel module # name = fd_d_path obj-m += $(name).o all: build .PHONY: build install clean build: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules CONFIG_DEBUG_SECTION_MISMATCH=y install: build -mkdir -p /lib/modules/`uname -r`/kernel/arch/x86/kernel/ cp $(name).ko /lib/modules/`uname -r`/kernel/arch/x86/kernel/ depmod /lib/modules/`uname -r`/kernel/arch/x86/kernel/$(name).ko clean: [ -d /lib/modules/$(shell uname -r)/build ] && \ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Relevant Link:
http://blog.csdn.net/cenziboy/article/details/7999217
4. 基于jprobe监控load_module()系统调用获取当前正在加载的LKM文件的绝对路径
关于jprobe的基本原理和内核代码原理,请参与其他的文章
http://www.cnblogs.com/LittleHann/p/3854977.html http://www.cnblogs.com/LittleHann/p/3920387.html
what we have?
1. jprobe可以在执行监控的系统调用的入口处得到回调通知进行执行 2. jprobe可以拿到和原始系统调用相同的参数列表,因为jprobe复制原始系统调用入口处的寄存器状态(注意: 是寄存器状态,不是内核栈状态) 3. 因为jprobe复制了寄存器状态,我们可以拿到的数据结构有 1) 当前正在加载的LKM的ELF镜像 2) CURRENT宏指向的地址是原始进程的task_struct(因为CURRENT是通过寄存器寻址的) 4. 通过task_struct可以间接地得到原始进程"打开的文件句柄列表"
那是不是说我们在监控的时候就可以直接得到原始insmod进程所加载的LKM的绝对路径呢?结论是否定的。
code
//打印当前进程的open files for (j = 0; j < 32; ++j) { get_path(current, j); } int get_path(struct task_struct *mytask, int fd) { struct file *myfile = NULL; struct files_struct *files = NULL; char path[100] = {'\0'}; char *ppath = path; files = mytask->files; if (!files) { printk("files is null..\n"); return -1; } //myfile = files->fdt->fd[fd]; myfile = files->fd_array[fd]; if (!myfile) { printk("myfile is null..\n"); return -1; } ppath = d_path(&(myfile->f_path), ppath, 100); printk("FULLPATH: %s\n", ppath); return 1; }
打印的结果是全null,0、1、2的是标准输入、输出、错误输出的file discriptor
为什么会这样呢?难道insmod不需要打开.ko文件再来加载吗?使用strace insmod hello.ko跟踪一下
从代码流程中可以清楚地看到,insmod在打开、读取了.ko文件之后,立刻关闭了原始文件,然后把ELF镜像直接传入系统调用init_module中,而我们知道,每个文件在内核中都有一个引用数,当前.ko文件只有insmod在打开它,则当前引用数减为0,这个文件在内核中的数据结构就会被释放,相应的,进程的文件句柄表fd_array也就不存在这个fd了,我们自然无法得到加载文件的路径
关于open、close的内核代码原理,可以参阅另一篇文章,对内核代码的分析可以明白,想要通过文件描述符这个渠道去获得当前进程之前打开过的文件的绝对路径是走不通的
http://www.cnblogs.com/LittleHann/p/3932624.html
这从某种程序上也体现了linux的设计思想,文件的存储和进程的运行是两个分立的东西,都是很细粒度的概念,我们通过进程要想回溯到磁盘上的文件,就需要有一个中间纽带,而在这个场景下,就是"文件句柄表",如果insmod的代码不要close这个文件,我们也就可以很容易地拿到了
Relevant Link:
http://www.makelinux.net/books/lkd2/ch12lev1sec10
5. 基于get_fs_pwd获取当前目录
2.6.39以上才能用 //获取.ko文件路径 /* get_fs_pwd(current->fs, &ko_pwd); ko_buf = (char*)__get_free_page(GFP_USER); ko_path = dentry_path_raw(ko_pwd.dentry, ko_buf, PAGE_SIZE); free_page((unsigned long)ko_buf); */ /* getcwd(buf, sizeof(buf)); */
待研究
Copyright (c) 2014 LittleHann All rights reserved