您的位置:首页 > 其它

内核态与用户态空间地址参数传递问题

2016-05-16 14:10 423 查看
原文转自:http://blog.chinaunix.net/uid-27411029-id-3383040.html, 记录下以供学习

关于在内核态、用户态数据通信,其中有一种方式就是直接的在内核态中获取用户态数据信息。应用场景如:在内核态中直接读取磁盘文件信息。

在这样的应用场景下,具体要解决的难点:如何使内核函数中的参数,合法的通过内核的保护模式的安全检查。比如 SYSCALL_DEFINE3(open...),SYSCALL_DEFINE3(read....),SYSCALL_DEFINE3(write..) 等文件相关的系统调用,在具体使用的时候,涉及的某些指针应该是从用户态传递过来的。而且在相应的系统调用处理流程中,会有相应参数的检查,看时候是传来
的用户态合法的参数。具体过程结合2.6.32.24源码分析如下:

1)open系统调用

pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { margin-bottom: 0.21cm; } pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { margin-bottom: 0.21cm; } fs/open.c

1053 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)

1054 {

1055 long ret;

1056

1057 if (force_o_largefile())

1058 flags |= O_LARGEFILE;

1059

1060 ret = do_sys_open(AT_FDCWD, filename, flags, mode);

1061 /* avoid REGPARM breakage on x86: */

1062 asmlinkage_protect(3, ret, filename, flags, mode);

1063 return ret;

1064 }

继续追踪do_sys_open:

1031 long do_sys_open(int dfd, const char __user *filename, int flags, int mode)

1032 {

1033 char *tmp = getname(filename);

1034 int fd = PTR_ERR(tmp);

1035

1036 if (!IS_ERR(tmp)) {

1037 fd = get_unused_fd_flags(flags);

1038 if (fd >= 0) {

1039 struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);

1040 if (IS_ERR(f)) {

1041 put_unused_fd(fd);

1042 fd = PTR_ERR(f);

1043 } else {

1044 fsnotify_open(f->f_path.dentry);

1045 fd_install(fd, f);

1046 }

1047 }

1048 putname(tmp);

1049 }

1050 return fd;

1051 }

关注的是关于文件名的处理。继续追踪filename

fs/namei.c

142 char * getname(const char __user * filename)

143 {

144 char *tmp, *result;

145

146 result = ERR_PTR(-ENOMEM);

147 tmp = __getname();

148 if (tmp) {

149 int retval = do_getname(filename, tmp);

150

151 result = tmp;

152 if (retval < 0) {

153 __putname(tmp);

154 result = ERR_PTR(retval);

155 }

156 }

157 audit_getname(result);

158 return result;

159 }

这里面主要看do_getname。

pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { margin-bottom: 0.21cm; } 113 /* In order to reduce some races, while at the same time doing additional

114 * checking and hopefully speeding things up, we copy filenames to the

115 * kernel data space before using them..

116 *

117 * POSIX.1 2.4: an empty pathname is invalid (ENOENT).

118 * PATH_MAX includes the nul terminator --RR.

119 */

120 static int do_getname(const char __user *filename, char *page)

121 {

122 int retval;

123 unsigned long len = PATH_MAX;

124

125 if (!segment_eq(get_fs(), KERNEL_DS)) {

126 if ((unsigned long) filename >= TASK_SIZE)

127 return -EFAULT;

128 if (TASK_SIZE - (unsigned long) filename < PATH_MAX)

129 len = TASK_SIZE - (unsigned long) filename;

130 }

131

132 retval = strncpy_from_user(page, filename, len);

133 if (retval > 0) {

134 if (retval < len)

135 return 0;

136 return -ENAMETOOLONG;

137 } else if (!retval)

138 retval = -ENOENT;

139 return retval;

140 }

在用户态缓冲区通过strncpy拷贝至内核态之前,前面就有关于filename参数的判断。其中 pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { margin-bottom: 0.21cm; }

arch/x86/include/asm/processor.h

909 /*

910 * User space process size: 3GB (default).

911 */

912 #define TASK_SIZE PAGE_OFFSET

913 #define TASK_SIZE_MAX TASK_SIZE

914 #define STACK_TOP TASK_SIZE

915 #define STACK_TOP_MAX STACK_TOP

这里可以看到,也就是判断filename这个指针的地址是否是大于3G空间的。我们都知道,进程拥有4G的线性地址空间,

其中用户态空间为0-3G,3G-4G为内核态地址空间。这里也就是判断filename这个地址是否是属于内核空间的,如果

是,则返回错误参数。

2)read系统调用

fs/read_write.c

372 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)

373 {

374 struct file *file;

375 ssize_t ret = -EBADF;

376 int fput_needed;

377

378 file = fget_light(fd, &fput_needed);

379 if (file) {

380 loff_t pos = file_pos_read(file);

381 ret = vfs_read(file, buf, count, &pos);

382 file_pos_write(file, pos);

383 fput_light(file, fput_needed);

384 }

385

386 return ret;

387 }

这里重点追踪vfs_read系统调用。

277 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)

278 {

279 ssize_t ret;

280

281 if (!(file->f_mode & FMODE_READ))

282 return -EBADF;

283 if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))

284 return -EINVAL;

285 if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))

286 return -EFAULT;



...................

这里我们关键的代码主要在于access_ok部分。

arch/x86/include/asm/uaccess.h主要是测试addr是否是有效的用户空间地址

68 *

69 * Context: User context only. This function may sleep.

70 *

71 * Checks if a pointer to a block of memory in user space is valid.

72 *

73 * Returns true (nonzero) if the memory block may be valid, false (zero)

74 * if it is definitely invalid.

75 *

76 * Note that, depending on architecture, this function probably just

77 * checks that the pointer is in the user space range - after calling

78 * this function, memory access functions may still return -EFAULT.

79 */

80 #define access_ok(type, addr, size) (likely(__range_not_ok(addr, size) == 0))

也就是access_ok宏,主要是用来检测其中第二个参数也就是addr是否是来自用户空间的,如果是,则正确通过。

如果不是,则错误返回。

3)write系统调用

389 SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,

390 size_t, count)

391 {

392 struct file *file;

393 ssize_t ret = -EBADF;

394 int fput_needed;

395

396 file = fget_light(fd, &fput_needed);

397 if (file) {

398 loff_t pos = file_pos_read(file);

399 ret = vfs_write(file, buf, count, &pos);

400 file_pos_write(file, pos);

401 fput_light(file, fput_needed);

.............................

与read系统调用及其类似,追踪vfs_write系统调用。

332 ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)

333 {

334 ssize_t ret;

335

336 if (!(file->f_mode & FMODE_WRITE))

337 return -EBADF;

338 if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))

339 return -EINVAL;

340 if (unlikely(!access_ok(VERIFY_READ, buf, count)))

341 return -EFAULT;

...................

这里能够看到,也同样是调用了access_ok系统调用。来判断数据是否是来自用户态空间。

如果我们需要从内核态直接读取磁盘文件该如何操作了?

内核已经为我们做好了接口。也就是set_fs/get_fs接口。具体的分析如下: pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { }

arch/x86/include/asm/uaccess.h

17 /*

18 * The fs value determines whether argument validity checking should be

19 * performed or not. If get_fs() == USER_DS, checking is performed, with

20 * get_fs() == KERNEL_DS, checking is bypassed.

21 *

22 * For historical reasons, these macros are grossly misnamed.

23 */

24

25 #define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })

26

27 #define KERNEL_DS MAKE_MM_SEG(-1UL)其实等同于oxFFFFFFFFUL (ul为无符号长整形,在X86上面是32位)

28 #define USER_DS MAKE_MM_SEG(TASK_SIZE_MAX)

29

30 #define get_ds() (KERNEL_DS)

31 #define get_fs() (current_thread_info()->addr_limit)

32 #define set_fs(x) (current_thread_info()->addr_limit = (x))

33

34 #define segment_eq(a, b) ((a).seg == (b).seg)

现看看get_fs:

current_thread_info()->addr_limit其具体的功能就是获取当前的thread_info结构体的addr_limit字段信息。起

具体分析,先以current_thread_info函数开始。

arch/x86/include/asm/thread_info.h

180 /* how to get the thread information struct from C */

181 static inline struct thread_info *current_thread_info(void)

182 {

183 return (struct thread_info *)

184 (current_stack_pointer & ~(THREAD_SIZE - 1));

185 }

pre { font-family: "Bitstream Vera Sans Mono",monospace; }p { margin-bottom: 0.21cm; }在现在的内核中,已经将thread_info结构体取代task_struct结构体,来和内核堆栈放到固定页面里面了。

所以从当前堆栈中,经过指定偏移,可以获取到当前thread_info结构体类型的指针。而thread_info结构体的

具体内容如下:

arch/x86/include/asm/thread_info.h

26 struct thread_info {

27 struct task_struct *task; /* main task structure */

28 struct exec_domain *exec_domain; /* execution domain */

29 __u32 flags; /* low level flags */

30 __u32 status; /* thread synchronous flags */

31 __u32 cpu; /* current CPU */

32 int preempt_count; /* 0 => preemptable,

33 <0 => BUG */

34 mm_segment_t addr_limit; 进程虚拟地址空间上限 也就是整个get_fs就是获取进程的地址空间上限。

对于set_fs宏,其处理过程也就是将进程的地址空间上限至为一个固定值。

那么如果我们直接在内核直接打开、读取、写入一个磁盘文件的内容的话,需要进行如下的处理。

mm_segment_t old_fs;

old_fs = get_fs();//获取当前的进程地址空间上限

set_fs(KERNEL_DS);//将进程的地址空间上限置为内核线程可以访问的的虚拟地址空间上限。也就是为4G。这时,进程的访问范围就变成了0~4G。

然后进行具体的打开、读取、写入操作代码。

set_fs(oid_fs);//最后还原进程原来的地址空间上限。

例子代码如下:

int open_file(char *filename, struct file **filp)

{

mm_segment_t oldfs;

oldfs = get_fs();

set_fs(KERNEL_DS);

*filp = filp_open(filename, O_RDONLY, 0);//打开文件

if (IS_ERR(*filp) || (NULL == *filp)) {

printk("unable to open file:%s\n", filename);

return PTR_ERR(*filp);

}

set_fs(oldfs);

return 0;

}

在内核模块中,就可以直接调用open_file("/home/xxx/1.txt"...),来直接的打开磁盘文件了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: