유저 공간에서 시스템 콜 아규먼트로 지정한 파일 이름은 커널 공간에 그대로 전달됩니다.
한 가지 예를 들어볼까요?
[https://elixir.bootlin.com/linux/v4.19.30/source/fs/open.c]
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
위 sys_open() 함수 첫 번째 아규먼트로 filename이 전달됩니다.
이번에는 다른 예시를 들겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/fs/open.c]
SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename,
umode_t, mode)
{
return do_fchmodat(dfd, filename, mode);
}
int do_fchmodat(int dfd, const char __user *filename, umode_t mode)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW;
위 코드를 보면 유저 공간에서 전달된 filename 아규먼트가 보입니다.
그런데 filename의 정체는 무엇일까요?
이는 유저 공간에 위치한 문자열 메모리 공간을 의미합니다.
그렇다면 커널 공간에서 이 문자열을 처리하려면 어떻게 해야 할까요?
"커널 공간에 버퍼를 잡고 문자열을 복사해야 합니다."
한 가지 예를 들어볼까요?
diff --git a/fs/open.c b/fs/open.c
index a003500..6e6e536 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -568,11 +568,17 @@ SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode)
return ksys_fchmod(fd, mode);
}
+#define USER_FILE_NAME "/proc/cmdline"
+
int do_fchmodat(int dfd, const char __user *filename, umode_t mode)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+
+ if(!strcmp(filename, USER_FILE_NAME)) {
+ printk("[+] filename: %s \n", filename);
+ }
retry:
error = user_path_at(dfd, filename, lookup_flags, &path);
if (!error) {
위 패치 코드의 목적은 다음과 같습니다.
"유저 공간에서 /proc/cmdline 퍼미션을 설정할 때 동작을 트레이싱하고 싶다."
그런데 문제는 이렇게 코드를 작성하면 커널 패닉이 발생합니다.
그렇다면 다음과 같이 코드를 작성하면 어떨까요?
diff --git a/fs/open.c b/fs/open.c
index 4dbbacc..55573c4 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -580,11 +580,29 @@ SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode)
return ksys_fchmod(fd, mode);
}
+static int debug_kernel_filename = 1;
+
+#define USER_FILE_NAME "/proc/cmdline"
int do_fchmodat(int dfd, const char __user *filename, umode_t mode)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW;
+
+ if (debug_kernel_filename) {
+ char buffer[256] = {0,};
+ if(filename) {
+ strncpy_from_user(&buffer[0], filename, sizeof(buffer) - 1);
+ buffer[sizeof(buffer) - 1] = '\0';
+
+ printk("[+][chmod] filename :%s \n", buffer);
+ if (!strncmp(USER_FILE_NAME, buffer, strlen(USER_FILE_NAME)) ) {
+ printk("[-] strncmp filename :%s \n", buffer);
+ }
+ }
+ }
+
+
retry:
error = user_path_at(dfd, filename, lookup_flags, &path);
if (!error) {
위 패치 코드의 원리는 간단합니다.
- strncpy_from_user() 함수로 유저 공간에 있는 문자열을 커널 스택 공간으로 복사
- strncmp() 함수를 써서 문자열 만큼 buffer와 비교
위 코드를 반영하고 커널 로그를 받으니 제대로 동작하네요.
[ 77.645675 / 01-01 00:01:38.189][6] Freeing unused kernel memory: 6784K
[ 77.652541 / 01-01 00:01:38.199][7] Run /init as init process
[ 77.875408 / 01-01 00:01:38.419][6] [+][chmod] filename :/proc/cmdline
[ 77.882229 / 01-01 00:01:38.429][6] [-] strncmp filename :/proc/cmdline
...
[ 78.635313 / 01-01 00:11:28.419][7] [+][chmod]] filename :/proc/self/fd/8
[ 78.735755 / 01-01 00:11:28.519][6] [+][chmod]] filename :/proc/self/fd/13
[ 78.743632 / 01-01 00:11:28.529][6] [+][chmod]] filename :/proc/self/fd/13
[ 78.751334 / 01-01 00:11:28.539][6] [+][chmod]] filename :/proc/self/fd/13
[ 78.758635 / 01-01 00:11:28.539][6] [+][chmod]] filename :/proc/self/fd/13
"혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답글 올려드리겠습니다!"






최근 덧글