커널 모듈과 같은 커널 영역에서 동작하는 코드들이 유저영역의 특정파일을 읽고 쓰는 것은 결코 좋은 방법이 아니다.
많은 커널 뉴비들이 이러한 방법을 질문하면 대부분의 대답은 "그러한 짓은 하지말라"이다. 대신에 다른 방법들을 추천하곤한다. proc이나 ioctl, sysfs을 이용하는 방법이다. 그렇다면 왜 그러한 짓을 하지 말라고 충고하는 것일까 ?
- 커널의 보호때문이다. 파일의 데이터를 interpret해가는 과정에서 유발되는 에러는 버퍼오버플로우 공격을 받을 수 있다.
- 정책문제이다. 특정 파일 시스템으로부터 특정위치에서 특정파일을 읽어야 하는 커널 모듈은 유저영역에서 제공해야 하는 파일에 의존하게 된다. 이것은 커널 개발자들이 최대한 피하려는 정책이다.
그렇다면 결코 파일을 읽을수 있는 방법이 없단 말인가?? 그건 아니다. 하지만 저자는 말하고 있다. 결코 이러한코드들을 남용하지말고 내가 알려줬다고 얘기하지도 말고.. 커널 코드에 submit될 코드에는 이런 코드들을 넣지 말라고..-도대체 왜 알려주는 거야
가장 간단한 방법은 다음과 같다.
fd = sys_open(filename, O_RDONLY, 0);
if (fd >= 0) {
/* read the file here */
sys_close(fd);
}
하지만 커널 영역에서 이러한 코드는 항상 실패하고 말 것이다. 이유는 다음과 같다.
- 커널은 sys_open에 넘어온 포인터가 유저영역에서 온것이라고 기대하고 있다. 그래서 유저영역에서의 포인터를 커널의 나머지 부분에서도 원활하게 사용할 수 있도록 바꾸려고 시도한다.
하지만 우리가 넘긴 파라미터는 커널 영역의 포인터이다. 이러한 어드레스 공간의 mismatch를 다루기 위해서 커널은 다음과 같은 함수를 제공한다. - get_fs()
- set_fs()
set_fs(KERNEL_DS);
set_fs에 valid한 인자는 KERNEL_DS(커널 데이터 세그먼트)와 USER_DS(유저 데이터 세그먼트)뿐이다. 또한 get_fs를 통하여 이전의 주소 제한을 획득할 수 있고, 주소 제한을 바꾸어 우리가 하고 싶은 일을 한후 원래데로 복귀 시킬 수 있다. 이러한 방법을 사용하면 최종적으로 다음과 같은 코드를 사용할 수 있다.
old_fs = get_fs();
set_fs(KERNEL_DS);
fd = sys_open(filename, O_RDONLY, 0);
if (fd >= 0) {
/* read the file here */
sys_close(fd);
}
set_fs(old_fs);
/etc/shadow를 읽는 코드는 다음과 같다.
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
static void read_file(char *filename)
{
int fd;
char buf[1];
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
fd = sys_open(filename, O_RDONLY, 0);
if (fd >= 0) {
printk(KERN_DEBUG);
while (sys_read(fd, buf, 1) == 1)
printk("%c", buf[0]);
printk("\n");
sys_close(fd);
}
set_fs(old_fs);
}
static int __init init(void)
{
read_file("/etc/shadow");
return 0;
}
static void __exit exit(void)
{ }
MODULE_LICENSE("GPL");
module_init(init);
module_exit(exit);
이번에는 write를 해볼 시간이다.
old_fs = get_fs();
set_fs(KERNEL_DS);
fd = sys_open(filename, O_WRONLY|O_CREAT, 0644);
if (fd >= 0) {
sys_write(data, strlen(data);
sys_close(fd);
}
set_fs(old_fs);
위의 코드는 이상없이 컴파일이 된다. 그러나 모듈을 로드하려고 할 때 다음과 같은 에러를 만나게 된다.
insmod: error inserting 'evil.ko': -1 Unknown symbol in module
위의 의미는 너의 모듈이 사용하려는 심볼은 export되지 않았고 커널내에서 사용할 수 없다는 것이다. 커널 로그를 살펴보면 어떤 심볼인지 알 수 있을 것이다.
evil: Unknown symbol sys_write
비록 sys_write가 syscalls.h 파일에 있다 하더라도 커널 모듈에서 사용되기 위해 export되어 있지는 않다. 그렇다면 sys_write를 만들면 된다.
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
static void write_file(char *filename, char *data)
{
struct file *file;
loff_t pos = 0;
int fd;
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
fd = sys_open(filename, O_WRONLY|O_CREAT, 0644);
if (fd >= 0) {
sys_write(fd, data, strlen(data));
file = fget(fd);
if (file) {
vfs_write(file, data, strlen(data), &pos);
fput(file);
}
sys_close(fd);
}
set_fs(old_fs);
}
static int __init init(void)
{
write_file("/tmp/test", "Evil file.\n");
return 0;
}
static void __exit exit(void)
{ }
MODULE_LICENSE("GPL");
module_init(init);
module_exit(exit);
원문의 코드를 그대로 옮겨 놓았다. 하지만 이상한 점이 있다. sys_write를 또 사용하고 있다는것이다. 개인적인 생각으로는 오타같다. sys_write대신에 vfs_write를 호출하고 있으므로 sys_write를 사용하는코드는 빠져야 할 것 같다. 테스트를 해보지 않아서 확실하진 않다는 점 유념해주기 바란다.
하지만 결코 좋은 방법은 아니다. 결코 이러한 방법을 사용하지 말아라!
기사 원문 http://www.linuxjournal.com/node/8110/print
한글 원문 http://wiki.kldp.org/wiki.php/%C4%BF%B3%CE%B3%BB%BF%A1%BC%ADReadWrite%BB%E7%BF%EB%C7%CF%B1%E2
KLDP 만세 :)
한글 원문 http://wiki.kldp.org/wiki.php/%C4%BF%B3%CE%B3%BB%BF%A1%BC%ADReadWrite%BB%E7%BF%EB%C7%CF%B1%E2
KLDP 만세 :)