call_usermodehelper() 사용법

Linux 커널은 일반적으로 사용자 공간(user space)에서 동작하는 프로그램을 직접 실행하지 않는다. 그런데 커널 모듈이나 커널 코드에서 특정 시점에 로그를 외부로 전송하거나 오류 발생 시 복구 스크립트를 호출하는 등, 사용자 공간 프로그램을 실행해야 하는 경우가 있다. 이런 용도로 커널은 call_usermodehelper()라는 API를 제공한다. 이 글에서는 call_usermodehelper()의 기본 개념과 사용법을 알아본다.

call_usermodehelper()란?

call_usermodehelper()는 커널 코드에서 사용자 공간의 프로그램을 실행할 수 있게 해주는 함수다. 커널이 명시적으로 지정한 바이너리와 인자, 환경 변수를 가지고 사용자 공간 프로그램을 실행한다.

int call_usermodehelper(const char *path, char **argv, char **envp, int wait);

각 매개변수는 다음과 같다.

매개변수설명
path실행할 사용자 공간 바이너리의 경로 (예: /bin/echo)
argv실행 인자 배열 (argv[0]은 프로그램 이름)
envp환경 변수 배열
wait실행 방식 플래그 (UMH_NO_WAIT, UMH_WAIT_PROC 등)

주요 사용 방식

wait 플래그

플래그설명
UMH_NO_WAIT0x00대기하지 않고 즉시 반환
UMH_WAIT_EXEC0x01자식 프로세스가 execve() 호출할 때까지만 대기
UMH_WAIT_PROC0x02자식 프로세스 종료 시까지 대기
UMH_KILLABLE0x04대기 중 kill 가능하도록 함

가장 많이 쓰는 옵션은 자식 프로세스의 종료(및 반환값)까지 확인하는 UMH_WAIT_PROC이다.

예제: echo 실행

커널 모듈에서 /bin/echo hello kernel 명령을 실행하는 예제다.

커널 모듈 예제 코드

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kmod.h>

static int __init my_module_init(void)
{
    char *argv[] = { "/bin/echo", "hello kernel", NULL };
    char *envp[] = { "HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

    int ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);

    printk(KERN_INFO "call_usermodehelper returned: %d\n", ret);
    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "Module exit.\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("call_usermodehelper Example");
MODULE_AUTHOR("YourName");

Makefile 예제

obj-m += call_helper.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

빌드 및 실행

make
sudo insmod call_helper.ko
dmesg | tail

결과

hello kernel
call_usermodehelper returned: 0

call_usermodehelper_setup / exec 방식

사용자 공간 프로세스를 더 세밀하게 컨트롤하려면 call_usermodehelper_setup()call_usermodehelper_exec()를 나눠서 사용할 수도 있다.

struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
    char **envp, gfp_t gfp_mask,
    int (*init)(struct subprocess_info *info, struct cred *new),
    void (*cleanup)(struct subprocess_info *), void *data);

int call_usermodehelper_exec(struct subprocess_info *info, int wait);

예제: call_usermodehelper_setup 사용

#include <linux/module.h>
#include <linux/kmod.h>

static int __init mod_init(void)
{
    struct subprocess_info *info;
    char *argv[] = { "/usr/bin/logger", "Hello from kernel", NULL };
    char *envp[] = { "HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

    info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC, NULL, NULL, NULL);
    if (!info)
        return -ENOMEM;

    return call_usermodehelper_exec(info, UMH_WAIT_PROC);
}

static void __exit mod_exit(void)
{
    printk(KERN_INFO "Exiting module.\n");
}

module_init(mod_init);
module_exit(mod_exit);

MODULE_LICENSE("GPL");

call_usermodehelper 작동 흐름

커널 공간 (Kernel)
   |
   | call_usermodehelper()
   v
subprocess_info 구조체 (exec path, argv, envp 등)
   |
   | fork() + execve()
   v
사용자 공간 프로그램

주의사항

  • 실행 파일 경로: /bin/echo 같은 명령어는 경로를 정확히 지정해야 하며, PATH 환경 변수는 명시적으로 넘겨줘야 한다.
  • 커널 로그 출력: printk() 메시지는 dmesg에 기록되지만, 실행된 사용자 공간 프로그램의 출력은 별도로 잡히지 않는다. 로그를 남기려면 logger 명령이나 /dev/kmsg에 쓰는 방식이 필요하다.
  • 사용자 공간 의존성: 대상 프로그램이 없어도 커널 모듈 자체는 로드되지만 실행 시 실패한다. BusyBox 환경 등에서는 경로가 다를 수 있으므로 존재 여부를 미리 확인해야 한다.
  • 권한: root 권한으로 동작하므로 신중히 써야 하고, 외부 입력값으로 실행 경로나 인자를 구성하는 것은 명령 주입(command injection) 위험이 있어 피해야 한다.

예제: 스크립트 실행

/usr/local/bin/myhook.sh 실행

char *argv[] = { "/usr/local/bin/myhook.sh", "arg1", "arg2", NULL };
char *envp[] = { "HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
int ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);

스크립트 쪽은 다음과 같이 인자를 받아 로그를 남기도록 작성할 수 있다. 실행 전 chmod +x로 실행 권한을 부여해야 한다.

#!/bin/bash
echo "call_usermodehelper called with $1 and $2" >> /tmp/kernel.log

디버깅 방법

dmesg로 커널 로그를 확인하고, 실행이 안 된다면 straceexecve 자체가 호출되는지, 대상 바이너리 경로가 맞는지부터 점검한다.

dmesg | tail -n 50
strace -e execve /bin/echo hello

사용자 공간에 로그를 남기고 싶다면 실행 프로그램을 logger로 바꿔서 syslog로 흘려보내는 방법도 있다.

char *argv[] = { "/usr/bin/logger", "Kernel event happened", NULL };

보안 고려사항

  • 입력 값 검증: 사용자 입력을 그대로 실행 경로나 인자에 넣지 않는다.
  • 실행 대상 제한: root 권한으로 실행되므로 신뢰된 프로그램만 호출한다.
  • SELinux / AppArmor: 정책으로 사용자 공간 실행을 추가로 제한할 수 있다.

참고 사이트

This Post Has One Comment

  1. PrimeBiome

    Your writing is like a breath of fresh air in the often stale world of online content. Your unique perspective and engaging style set you apart from the crowd. Thank you for sharing your talents with us.

답글 남기기