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_WAIT | 비동기로 실행. 즉시 반환 |
UMH_WAIT_PROC | 자식 프로세스 종료 시까지 대기 |
UMH_WAIT_EXEC | 자식 프로세스가 execve() 호출 후 반환 |
일반적으로 가장 많이 사용하는 옵션은
UMH_WAIT_PROC
이다.
Example: 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()
함수를 사용할 수도 있다.
Example: 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
+----------------------+
| 사용자 공간 프로그램 |
+----------------------+
주의사항
1. 실행 파일의 경로 문제
/bin/echo
같은 명령어는 경로를 정확히 지정해야 하며,PATH
환경 변수는 명시적으로 제공해야 한다.
2. 커널 로그 외부로 출력
printk()
메시지는 dmesg에 기록된다.- 사용자 공간에서 로그를 기록하려면
logger
명령이나/dev/kmsg
에 쓰는 방식이 필요하다.
3. 사용자 공간 의존성
- 사용자 공간 프로그램이 없어도 커널 모듈은 로드되지만, 실행 시 실패할 수 있다.
- BusyBox 환경 등에서는 경로가 다를 수 있으므로 반드시 존재 여부 확인 필요.
4. 권한 제한
- 루트 권한으로 동작하므로 신중히 사용해야 한다.
- 보안 이슈가 될 수 있으므로 외부 입력값을 통한 command 실행은 항상 주의해야 한다.
Example: 스크립트 실행
/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);
스크립트 예
#!/bin/bash
echo "call_usermodehelper called with $1 and $2" >> /tmp/kernel.log
반드시
chmod +x
로 실행 권한 부여
디버깅 방법
1. dmesg
출력 확인
dmesg | tail -n 50
2. 프로그램 실행 실패 확인
strace -e execve /bin/echo hello
3. 로그 파일로 출력 유도
char *argv[] = { "/usr/bin/logger", "Kernel event happened", NULL };
call_usermodehelper 사용 시의 보안 고려사항
- 입력 값 검증: 사용자 입력을 통한 경로 지정은 절대 금지.
- 실행 제한: root 권한을 사용하므로 반드시 신뢰된 프로그램만 실행.
- SElinux / AppArmor 정책: 사용자 공간 실행 제한을 적용할 수 있음.