Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

2038
469
422320


[리눅스커널] 시스템 콜: 유저 공간에서 전달한 문자열 처리 방법 11. 시스템 콜

유저 공간에서 시스템 콜 아규먼트로 지정한 파일 이름은 커널 공간에 그대로 전달됩니다.

한 가지 예를 들어볼까요?
[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


"혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답글 올려드리겠습니다!"




덧글

  • 영악한 얼음여왕 2019/07/20 18:42 # 답글

    안녕하십니까 좋은 글 매번 잘 보고있습니다. 보다가 의문점이 생겨서 이렇게 답글을 남깁니다. 커널에서 __user * filename으로 user space에서 입력값을 받아오게되면 상대경로로 들어올 경우에는 시스템입장에서 파일을 못찾을 것 같다는 생각을 했습니다. 그래서 분명히 상대경로를 절대경로로 바꾸어주는 그런 함수가 있을 것 같다는 생각을 했습니다. 혹시 이런 함수가 있거나 혹은 제가 코드를 추가하여 상대경로를 절대경로로 바꾸어 줄수있는 방법이 있나요?
  • 영악한 얼음여왕 2019/07/24 12:35 # 답글

    이렇게 부탁드려서 죄송합니다 여러방법을 시도해보고있지만 되지 않습니다.. 한번만 도와주실수 있나요..?
  • AustinKim 2019/07/30 23:09 #

    제가 개발 업무로 너무 바빠 자세한 커맨트를 못 드리고 있습니다.

    어떤 시나리오인지는 모르겠지만 가급적이면 커널에서 제공하는 함수를 사용해보시겠어요?
    일일이 커널 함수를 제작하면 시간이 오래 걸리고 고생만 할 때가 많은 것 같습니다.

    현재 경로를 알아 낼 수 있는 함수는 시스템 콜 핸들러인 sys_getcwd()입니다.
    [https://elixir.bootlin.com/linux/v4.19.30/source/fs/d_path.c]
    SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
    {
    int error;
    struct path pwd, root;

    파일 경로를 절대 경로로 변환하려면 다음 커널 함수를 활용하셔도 좋습니다.
    filename_lookup()
    d_absolute_path()
  • AustinKim 2019/07/30 23:09 # 답글

    제가 개발 업무로 너무 바빠 자세한 커맨트를 못 드리고 있습니다.

    어떤 시나리오인지는 모르겠지만 가급적이면 커널에서 제공하는 함수를 사용해보시겠어요?
    일일이 커널 함수를 제작하면 시간이 오래 걸리고 고생만 할 때가 많은 것 같습니다.

    현재 경로를 알아 낼 수 있는 함수는 시스템 콜 핸들러인 sys_getcwd()입니다.
    [https://elixir.bootlin.com/linux/v4.19.30/source/fs/d_path.c]
    SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
    {
    int error;
    struct path pwd, root;

    파일 경로를 절대 경로로 변환하려면 다음 커널 함수를 활용하셔도 좋습니다.
    filename_lookup()
    d_absolute_path()
  • kim_s 2019/08/02 03:23 # 삭제 답글

    시스템 콜 함수를 커널 내에서 사용하는 방법이 있나요?
  • AustinKim 2019/08/02 07:08 #

    호출할 수 있습니다. 예를 들어볼까요?

    만약 시스템 콜 핸들러 함수가 다음과 같이 선언돼 있으면,
    SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)

    적절한 인자와 함께,
    sys_getcwd() 함수를 호출하면 됩니다.
  • AustinKim 2019/08/02 07:08 # 답글

    호출할 수 있습니다. 예를 들어볼까요?

    만약 시스템 콜 핸들러 함수가 다음과 같이 선언돼 있으면,
    SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)

    적절한 인자와 함께,
    sys_getcwd() 함수를 호출하면 됩니다.
댓글 입력 영역