2)来个实现函数:

122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg)

123 {

124 int ret = 0;

125 struct _test_t *dev = filp->private_data;

126 struct ioctl_data val;

127

128 /*既然这么费劲定义了命令,当然要检验命令是否有效*/

129 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;

130 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL;

131

132 switch(cmd){

133 case TEST_CLEAR:

134 memset(dev->kbuf, 0, DEV_SIZE);

135 dev->cur_size = 0;

136 filp->f_pos = 0;

137 ret = 0;

138 break;

139 case TEST_OFFSET: //根据传入的参数更改偏移量

140 filp->f_pos += (int)arg;

141 P_DEBUG("change offset!\n");

142 ret = 0;

143 break;

144 case TEST_KBUF: //修改kbuf

145 if(copy_from_user(&val, (struct ioctl_data *)arg, sizeof(struct ioctl_data))){

146 ret = - EFAULT;

147 goto RET;

148 }

149 memset(dev->kbuf, 0, DEV_SIZE);

150 memcpy(dev->kbuf, val.buf, val.size);

151 dev->cur_size = val.size;

152 filp->f_pos = 0;

153 ret = 0;

154 break;

155 default: /*命令错误时的处理*/

156 P_DEBUG("error cmd!\n");

157 ret = - EINVAL;

158 break;

159 }

160

161 RET:

162 return ret;

163 }

145行,因为指针是从用户程序传来,所以必须检查安全性。


 

3)来个应用程序

9 int main(void)

10 {

11 char buf[20];

12 int fd;

13 int ret;

14

15 struct ioctl_data my_data= {

16 .size = 10,

17 .buf = "123456789"

18 };

19

20 fd = open("/dev/test", O_RDWR);

21 if(fd < 0)

22 {

23 perror("open");

24 return -1;

25 }

26

27 write(fd, "xiao bai", 10);

28

29 ioctl(fd, TEST_KBUF, &my_data);

30

31 ret = read(fd, buf, 10);

32 printf("<app> buf is [%s]\n", buf);

33 if(ret < 0)

34 {

35 perror("read");

36 }

37

38 close(fd);

39 return 0;

40 }


 

4)再来验证一下:

[root: 4th]# ./app

<kernel>[test_write]write 10 bytes, cur_size:[10]

<kernel>[test_write]kbuf is [xiao bai]

<kernel>[test_read]read 10 bytes, cur_size:[0]

<app> buf is [123456789] //成功!

注:类似copy_xx_user的函数含有put_userget_user等,我就不细说了。

 

下面说第二种方法:进入ioctl后使用access_ok检测。

声明一下:下面的验证方法是不正确的。如果不想看下去的话,今天的内容已经讲完了。


 

先说一下access_ok的使用

access_ok(type, addr, size)

使用:检测地址的安全性

参数:

type:用于指定数据传输的方向,VERIFY_READ表示要读取应用层数据,VERIFT_WRITE表示要往应用层写如数据。注意:这里和IOR IOW的方向相反。如果既读取又写入,那就使用VERIFY_WRITE

addr:用户空间的地址

size:数据的大小

返回值:

成功返回1,失败返回0


 

既然知道怎么用,就直接来程序了:

1)定义命令

1 #ifndef _TEST_CMD_H

2 #define _TEST_CMD_H

3

4 struct ioctl_data{

5 unsigned int size;

6 char buf[100];

7 };

8

9 #define DEV_SIZE 100

10

11 #define TEST_MAGIC 'x' //定义幻数

12 #define TEST_MAX_NR 3 //定义命令的最大序数

13

14 #define TEST_CLEAR _IO(TEST_MAGIC, 1)

15 #define TEST_OFFSET _IO(TEST_MAGIC, 2)

16 #define TEST_KBUF _IOW(TEST_MAGIC, 3, struct ioctl_data)

17

18 #endif /*_TEST_CMD_H*/

这里终于要用_IOW了!


 

2)实现ioctl

122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg)

123 {

124 int ret = 0;

125 struct _test_t *dev = filp->private_data;

126

127 /*既然这么费劲定义了命令,当然要检验命令是否有效*/

128 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;

129 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL;

130 /*根据提取命令指定的方向判断指针的安全性*/

131 if(_IOC_DIR(cmd) & _IOC_READ)

132 ret = access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));

133 else if(_IOC_DIR(cmd) & _IOC_WRITE)

134 ret = access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));

135 if(!ret) return - EFAULT;

136

137 switch(cmd){

138 case TEST_CLEAR:

139 memset(dev->kbuf, 0, DEV_SIZE);

140 dev->cur_size = 0;

141 filp->f_pos = 0;

142 ret = 0;

143 break;

144 case TEST_OFFSET: //根据传入的参数更改偏移量

145 filp->f_pos += (int)arg;

146 P_DEBUG("change offset!\n");

147 ret = 0;

148 break;

149 case TEST_KBUF: //修改kbuf

150 memset(dev->kbuf, 0, DEV_SIZE);

151 memcpy(dev->kbuf, ((struct ioctl_data *)arg)->buf,

152 ((struct ioctl_data *)arg)->size);

153 dev->cur_size = ((struct ioctl_data *)arg)->size;

154 filp->f_pos = 0;

155 ret = 0;

156 break;

157 default: /*命令错误时的处理*/

158 P_DEBUG("error cmd!\n");

159 ret = - EINVAL;

160 break;

161 }

162

163 return ret;

164 }

上面并没有用copy_to_user,而是通过access_ok来检测。


 

3)再来个应用程序:

9 int main(void)

10 {

11 char buf[20];

12 int fd;

13 int ret;

14

15 struct ioctl_data my_data= {

16 .size = 10,

17 .buf = "123456789"

18 };

19

20 fd = open("/dev/test", O_RDWR);

21 if(fd < 0)

22 {

23 perror("open");

24 return -1;

25 }

26

27 write(fd, "xiao bai", 10);

28

29 ret = ioctl(fd, TEST_KBUF, &my_data);

30 if(ret < 0)

31 {

32 perror("ioctl");

33 }

34

35 ret = read(fd, buf, 10);

36 printf("<app> buf is [%s]\n", buf);

37 if(ret < 0)

38 {

39 perror("read");

40 }

41

42 close(fd);

43 return 0;

44 }


 

4)验证一下:效果和上一个一样

[root: 5th]# ./app

<kernel>[test_write]write 10 bytes, cur_size:[10]

<kernel>[test_write]kbuf is [xiao bai]

<kernel>[test_read]read 10 bytes, cur_size:[0]

<app> buf is [123456789]


 

下面就要如正题了,这个驱动是有问题的,那就是验证安全性完全不起作用!当我传入非法指针时,驱动同样会输出,不信可以自己传个邪恶地址(void *)0进去试一下。


 

修改应用程序一样代码:

29 ret = ioctl(fd, TEST_KBUF, &my_data);


 

上面是我做的错误实现,我本来想验证,只要经过access_ok检验,数据就会安全,没想到经过access_ok检验之后照样会出错。

但是,copy_to_user同样是先调用access_ok再调用memcpy,它却没出错。这个我事情我现在都没搞明白,如果谁知道了麻烦指点一下。


 

我查了设备驱动第三版,在144页有这样的说法:

1.access_ok并没有做完的所有的内存检查,

2.大多数的驱动代码都不是用access_ok的,后面的内存管理会讲述。


 

在这里书本上有这样的约定:(都是我自己的理解)

1.传入指针需要检查安全性。memcpy函数尽量不要在内核中使用。

2.copy_to_user.copy_from_user.get_user.put_user函数会再拷贝数据前检测指针的安全性。不需要access_ok

3.如果在ioctl函数开头使用了accsee_ok检验数据,接下来的代码可以使用__put_user__get_user这些不需要检测的函数(书上有例子)


 

虽然还有写东西还没搞懂,但个人觉得,如果使用个access_ok要这么麻烦的话,那我就不用好了,以后我就使用copy_xx_user函数,省力又省心。


 


 


 

七、总结:


 

这次讲了ioctl的实现:

1)命令是怎么定义。

2)参数怎么传递。