까나리 in kernel

Linux 2011. 4. 8. 13:48
커널 부팅 시퀀스를 분석하다가, 흥미로운(?) 이름의 함수를 발견했다.

boot_init_stack_canary();


도대체 canary가 뭘까? 영어사전을 찾아보니 우리가 알고 있는 조막막한 노란새인 카나리아외엔 다른 의미가 없단다.

뭘까 뭘까 하고 검색을 하다가, 찾아냈다.  결론부터 말하면 유독가스에 민감한 카나리아를 광부들이 광산에 데리고 간데서 유래됐다고 하는데, 이러한 사실을 기반으로 작명한 것치곤 작명센스가 작살이다.

http://studyfoss.egloos.com/5279959

이 링크를 보면 stack canary가 무엇을 위한 것인지 알 수 있다.  x86쪽만 되어있는데, mips나 다른 platform도 확인해보도록 하자. (다음 기회에.. ㅋㅋ 여긴 회사니까..)
Posted by 강군님

댓글을 달아 주세요

Linux kernel에서 usb-storage 등을 꽂으면 자동으로 인식되어 mount까지 되는 것을 볼 수 있는데, 이것을 hotplug라고 한다.  Hotplug가 일어나는 과정은 대략 다음과 같이 기술할 수 있다.

1. 장비 꽂음

2. Kernel의 interrupt routine을 거친 후 kobject에서 인식

3. uevent가 발생하고 user application인 hotplug routine 실행

따라서 이러한 hotplug signal을 받아낼 수 있다면 새로운 장치가 인식되었을 때에 원하는 작업을 수행할 수 있게 된다.  이 문서에서는 이를 가능하게 하는 방법을 기술하는 것을 목표로 한다.

Kernel 2.6.17에서 uevent에 관련된 정보는 lib/kobject_uevent.c에서 찾아볼 수 있다.

void kobject_uevent (struct kobject *kobj, enum kobject_action action)

{

  char **envp;

  char *buffer;

  char *scratch;

  const char *action_string;

  const char *devpath = NULL;

  const char *subsystem;


  ...

 

  /* call uevent_helper, usually only enabled during early boot */

  if (uevent_helper[0]) {

    char *argv[3];

  

    argv[0] = uevent_helper;

    argv[1] = (char *) subsystem;

    argv[2] = NULL;

    call_usermodehelper (argv[0], argv, envp, 0);

    /* send signal to detect daemon routine end */.

  }

  

  ...

}


모든 routine이 실행되고 마지막으로 call_usermodehelper ()를 호출하여 user 영역의 application을 실행한다.  현재 uevent_helper는 “/sbin/hotplug”이다.

따라서, signal을 받기 원하는 daemon에게 이러한 signal을 전달해줄 user application을 작성하고, 이 uevent_helper에 등록하면 실행될 것이다.  여기서는 이러한 user application이 /root/detect_test라고 가정하도록 하겠다.

...


  /* call uvent_helper, usually only enabled during early boot */

  if (uevent_helper[0]) {

    char *argv[3];


    argv[0] = uevent_helper;

    argv[1] = (char *) subsystem;

    argv[2] = NULL;

    call_usermodehelper (argv[0], argv, envp, 0);


    /* edit part start */

    if (0 == strcmp (“mount”, action_string) || 0 == strcmp (“unmount”, action_string)) {

      argv[0] = “/root/detect_test”;

      call_usermodehelper (argv[0], argv, envp, 0);

    }

    /* edit part end */


    /* send signal to detect daemon routine end */

  }

  

  ...

}


이렇게 작성한다면 장비를 꽂거나 제거할 때에 /root/detect_test라는 user application이 호출된다.  이때 /root/detect_test에 전달된 argv와 envp를 사용하면 여러가지 정보를 알 수 있다.

/root/detect_test

block


HOME=/

PATH=/sbin;/bin;/usr/sbin;/usr/bin;

ACTION=mount

DEVPATH=/block/sda/sda1

SUBSYSTEM=block

SEQNUM=706

MINOR=1

MAJOR=8

PHYSDEVPATH=/devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0

PHYSDEVBUS=scsi

PHYSDEVDRIVER=sd


2010년 3월 10일에 추가함
============================================
2.6.23.17 kernel에서 확인한 결과, user helper를 사용하지 않고도 kernel과 user app을 연결시키는 방법이 아래와 같다.

   
    int ns = -1;
    struct sockaddr_nl sa;
    char buf[1024];
    struct iovec iov = { buf, sizeof(buf) };
    struct msghdr msg = { &sa, sizeof(sa),&iov, 1, NULL, 0, 0};
    int len;

    (void)params;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;

    ns = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    if(ns < 0)
    {
        PrintError("[%s]%d socket()",__func__,__LINE__);
        return;
    }

    if(bind(ns, (struct sockaddr *)&sa, sizeof(sa)))
    {
        PrintError("[%s]%d bind()",__func__,__LINE__);
        close(ns);
        return;
    }

    while(1) {
        len = recvmsg(ns, &msg, 0);
        ...
    }

    close(ns);

RAW socket을 이용하여 recv가 가능하구나... 이제야 알았다... -.-;;

Posted by 강군님

댓글을 달아 주세요

  1. Sunny 2012.02.13 11:39  댓글주소  수정/삭제  댓글쓰기

    올려주신 post잘 보았습니다. uevent_helper를 사용하지 않고 socket으로 kobject_uevent를 받아서 처리해보려고 하는데, 막상 recvmsg()까지 와서 kobject까지 얻어내는 방안이 묘연합니다.
    예를 들어 usb가 꽂혔을 때 uevent가 발생하고 난 후 "/sbin/hotplug"를 실행시키고자 하는데,(물론 default로 들어가있긴하지만 만약 없다고 가정하면) 이를 실행시키려면 subsystem을 넣어줘야 다음과 같이 호출이 가능합니다.

    call_usermodehelper ("/sbin/hotplug", (char *)subsystem, NULL, 0); (linux/lib/kobject_uevent.c참고)
    여기서 subsystem은 kobject로부터 받아오게 되는데요.
    recvmsg로 uevent를 받고서 msghdr로부터 kobject를 받아와야 되는걸로 보이는데, 방법이 잘 찾아지지 않네요. .ㅠ.ㅠ 도움 부탁드립니다

  2. Favicon of https://kangun.tistory.com 강군님 2012.02.15 10:09 신고  댓글주소  수정/삭제  댓글쓰기

    출장지라서 답글을 늦게 보았습니다 ^^;; 그런데 무엇을 원하시는 것인지요?

    지금 올리신 내용으로는 잘 이해가 안되서요.. 알고 계시겠지만 위쪽에 있는 code는 kernel space에서의 것이고, 아래쪽에 새로 넣은 것은 user space에서의 code입니다.

    무엇을 원하시는 것인지 잘 이해를 못해서... 뭐라고 말씀드리기가 힘드네요 ^^

  3. Sunny 2012.02.17 15:40  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 답글 감사드립니다.
    제가 두 개의 구현 방법을 참고삼아, 보던중에, 실제 구현은 아래 user space의 code를 작업하던 중이었는데, 하다가 막히는 바람에 글을 올리게 되었습니다. 실제 kernel space에서 작업을 하게 되면 관련 환경 변수들(envp)이 넘어가게 되니까 문제가 없는데, user space에서 recvmsg로 받고 난 후에 이 envp를 얻는 것에서 막혔습니다. 실제 envp가 넘어가질 않으니 제가 user space에서 user event를 받아도 제대로 처리가 되지 않더군요.

    아 참고로 제가 작업하데 된 background는 proc/sys/kernel/hotplug에 sbin/hotplug를 넣지 않는 상황에서 user event를 받아 이 sbin/hotplug를 trigger하려는 것입니다.

  4. Favicon of https://kangun.tistory.com 강군님 2012.03.14 09:20 신고  댓글주소  수정/삭제  댓글쓰기

    헐~ 답글이 굉장히 많이 늦어졌네요.. ^^;;; 아직도 출장지에 있습니다.. ㅎㅎ 집이 그립네요 -.-

    실제로 말씀하신 것처럼 envp가 넘어오지는 않습니다만, 넘어온 정보들로 sdXn인지 block device인지 등등을 알 수 있습니다. 넘겨진 정보들을 사용하여 /sys의 값들을 활용하여도 찾을 수가 있고요..

    다만 kernel space에서 바로 넘겨준 것처럼 보기 쉽게(?) 정리가 되지는 않긴 하네요.. 방법을 한번 찾아봐야겠군요.. ㅎㅎ (이글을 보실지도 의문입니다만... ^^)

이 문서를 읽기 전에 우선 숙지해야 할 사항은 kernel 영역에서 user 영역의 file에 대해 read/write를 사용하는 것은 절대 좋지 않은 정책이라는 것이다.  첫번째 이유는 커널의 보호문제이다.  File의 data를 interrupt해가는 과정에서 유발되는 error는 buffer overflow 공격을 받을 가능성이 있다.  두번째 이유는 정책의 문제이다.  특정 fle system으로부터 특정 위치에서 특정 file을 읽어야 하는 kernel module은 user 영역에 의존성을 가지게 된다.  이것은 kernel developer들이 가장 피하는 정책이기도 하다.

이럼에도 불구하고 kernel 영역에서 user 영역의 file을 handling하여야 하는 경우는 종종 발생하는데, 이런 경우에서의 handling 하는 방법을 정리하는 것이 이 문서의 목표이다.

#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];

    loff_t pos = 0;

    struct file *file;

    mm_segment_t old_fs = get_fs ();


    set_fs (KERNEL_DS);

    fd = sys_open (filename, O_RDONLY, 0);

    if (fd >= 0)

    {

        file = fget (fd);

        if (file)

        {

            vfs_read (file, buf, sizeof (buf), &pos);

            printk (“buf is %c\n”, buf[0]);

        }

        sys_close (fd);

    }

    set_fs (old_fs);

}


static void write_file (char *filename, char *data)

{

    int fd;

    loff_t pos = 0;

    struct file *file;

    mm_segment_t old_fs = get_fs ();


    set_fs (KERNEL_DS);

  

    fd = sys_open (filename, ORWONLY | O_CREAT, 0644);

    if (fd >= 0)

    {

      file = fget (fd);

      if (file)

      {

          vfs_write (file, data, strlen (data), &pos);

          fput (file);

      }

    }

      set_fs (old_fs);

}


static int __init init (void)

{

    printk (“<0> file handling start\n”);

    read_file (“/etc/shadow”);

    write_file (“/etc/temp”, “foo”);

    printk (“<0> file handing end\n”);


    return 0;

}


static void __exit exit (void)

{

    printk (“<0> bye\n”);

}


MODULE_LICENSE (“GPL”);

module_init (init);

module_exit (exit);


mm_segment_t get_fs (void) / void set_fs (mm_segment_t) 

Kernel 은 sys_open에서 전달된 pointer가 user 영역에서 온 것이라고 판단한다.  따라서 이를 kernel 영역에 맞도록 전환하려고 하는데 이 예제에서는 kernel 영역에서 pointer를 넘긴 것이므로 여기서 발생하는 mistmach를 제거하기 위해 사용된다.  set_fs ()에서 사용하는 parameter는 KERNEL_DS (kernel segment), USER_DS (user segment) 뿐이며, get_fs ()는 parameter 없이 현재의 segment 정보를 가져온다.


long sys_open (const char *, int, int) / long sys_close (unsigned int)

System call인 open / close가 kernel 내부에서 불리워지는 형태이며 parameter도 동일하다.


struct file *fget (unsigned int) / void fput (struct file *)

sys_open을 통해 생성된 fd를 vfs_ 계열의 함수에서 사용할 수 있는 struct file *의 형태로 변환시켜주고, fput ()으로 쓰여진 buffer를 sync한다.


ssize_t vfs_write (struct file *, const char *,size_t, loff_t)
       ssize_t vfs_read (struct file *, char *, size_t, loff_t)

System call인 write / read가 kernel 내부에서 불리워지는 형태이며 parameter도 동일하다. 

Posted by 강군님

댓글을 달아 주세요

어떤 리눅스 커널 모듈을 만들었다고 치고.


insmod xxx.ko test=hello world


저러면 원하는대로 test에 hello world라는 값을 넣을 수 없다.

요건 어찌하면 좋을까?

쉘에서 분리하지 않게 test=”hello world” 해도 안된다.

insmod.c를 살펴보면,

for (i = 2; i < argc; i++) {
options = realloc(options,
strlen(options) + 1 + strlen(argv[i]) + 1);
strcat(options, argv[i]);
strcat(options, " ");
}
.
.
.
ret = init_module(file, len, options);

요게 전부다.

따로 strtok를 하지도 않고 그냥 붙여서 init_module 해버린다.

그런데 저 init_module을 사실은 시스템콜이다. 본체를 찾아보니 kernel/module.c 에 @sys_init_module@이 있다.

요녀석은 다시

mod = load_module(umod, len, uargs);

로 넘긴다.

이녀석을 찾아보면,

args = strndup_user(uargs, ~0UL >> 1);
.
.
.

mod->args = args;
if (obsparmindex)
printk(KERN_WARNING "%s: Ignoring obsolete parameters\n",
mod->name);

/* Size of section 0 is 0, so this works well if no params */
err = parse_args(mod->name, mod->args,
(struct kernel_param *)
sechdrs[setupindex].sh_addr,
sechdrs[setupindex].sh_size
/ sizeof(struct kernel_param),
NULL);

뭐 이런걸 찾을 수 있다.

parse_args를 찾아 kernel/params.c로 뛰어보면,


args = next_arg(args, &param, &val);
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, params, num, unknown);

슬슬 목표에 가까워지는 기분이다.

우선 next_arg



static char *next_arg(char *args, char **param, char **val)
{
unsigned int i, equals = 0;
int in_quote = 0, quoted = 0;
char *next;

if (*args == '"') {
args++;
in_quote = 1;
quoted = 1;
}

“로 시작하면 인용문자열이라고 인식하는거다.


for (i = 0; args[i]; i++) {
if (args[i] == ' ' && !in_quote)
break;
if (equals == 0) {
if (args[i] == '=')
equals = i;
}
if (args[i] == '"')
in_quote = !in_quote;
}

이제 공백이 나왔는데 인용문자열이 아니면 끝내는거다. 바로 요거다. 공백을 넣으려면 “로 싸야한다. 그 다음은 = 이 나왔을때 등호의 위치를 기억해두는거다. 그리고 그 다음은 quote상태를 토글하는거고.

그 밑에는 등호의 위치를 이용해서 param과 val를 나누고, val에서 “를 빼는 따위의 짓을 한다.

이제 대충 결론이 났다. test에 Hello world를 넣으려면,


insmod xxx.ko 'test="Hello world"'

라고 해주면 되겠다.

From 미스랜더의 삽질 공작소
Posted by 강군님

댓글을 달아 주세요

ubuntu 커널 컴파일

Linux 2007. 10. 30. 14:09
ubuntu 에서 커널 컴파일은 다른 배포판의 kernel compile과 틀릴 것이 별로 없지만, dpkg를 이용한 조금 더 색다른(?) 방법이 존재한다.

당연히 커널도 프로그램이므로, build-essential package가 필요하다. 그외에, kernel-package 및 libncurses-dev를 설치해야 빌드 과정에 문제가 없을 것이다 (libncurses는 make menuconfig에서 필요).

커널 컴파일을 위해 fakeroot등을 사용하기도 하는데, 어차피 root가 아니면 커널을 컴파일할 필요가 없으니, 아예 root로 접근하기로 한다.

$ sudo su -

성공적으로 root로 로그인 됐다면, 프롬프트가 #으로 바뀔 것이다.

# 이 상태에서 wget을 사용하거나 웹에서 컴파일 하고자하는 커널의 소스를 다운 받은 뒤, /usr/src 디렉토리에 압축을 해제하고, 심볼릭 링크를 걸어주도록 한다.

# tar xjvf linux-2.6.23.1.tar.bz2
# ln -s linux-2.6.23.1 linux

많은 linux module의 경우 /usr/src/linux 아래에서 컴파일에 필요한 header file 등을 찾기에, 이렇게 해주는 것이 훨씬 편할 것이다.

압축 해제가 되었다면, 컴파일 설정을 해주도록 한다.

# cd /usr/src/linux
# make menuconfig

ncurses 라이브러를 사용한 컴파일 설정 화면이 뜨고, 여기서 값들을 셋팅해주는데, *은 image로 포함될 것, M은 module로 설치될 것, 빈칸은 당연히 컴파일 하지 않는다. 문제는 여기서부터인데, image에 포함되어야할 필수적인 것들 (예를 들면 sata disk driver 등)을 포함하지 않거나 module로 설정이 되면, 커널 패닉이라는 최악의 경우가 발생하므로, 미리 정보를 수집하여야 한다.

정보를 수집하는 방법으로는 여러가지가 있으나, 가장 좋은 경우는 당연히 자신의 시스템에 대해서 완벽히 알고 있는 것이라고 할 수 있겠다. 하지만 사실상 불가능하므로, ubuntu가 설치될 때 빌드된 설정을 가져다 쓰는 것도 좋은 방법이다. 이 설정 파일들도 /usr/src/linux-headers-2.6.20-16-generic/.config으로 존재하므로, 이곳에 있는 설정을 가져다 쓰도록 한다. 하지만 버전 차이가 너무 많이 나면 문제가 발생할 수도 있으므로, 이전의 디렉토리에서 make menuconfig 이후 하나씩 체크해가며 설정을 하는 것이 좋겠다. 여기에 lspci, lsusb, lsmod 등을 활용하여 실제로 자신의 시스템에서 활용되고 있는 device 및 module을 선택하면 되겠다.

설정이 마무리 되었으면, 본격적으로 컴파일을 시작한다.

linux source 압축 해제한 디렉토리에서 다음과 같이 한다.

# make-kpkg clean
# make-kpkg --append-to-version=0.1 kernel_image --initrd binary

이렇게 하면, 자신이 선택한 항목에 따라 빌드 시간이 좌우된다. 빌드가 마무리 되면, 빌드된 image와 module을 설치하도록 한다.

# cd /usr/src
# dpkg -i linux-image-2.6.23.1.1_2.6.23.1.1-10.00.Custom_i386.deb

이것으로 image 및 module 설치, 그리고 grub(혹은 lilo) 설정까지 마무리 되었다. 혹시 bootloader 환경을 변경하고 싶다면 /boot/grub/menu.lst를 수정하도록 한다.

이외에 package holding 등에 대한 내용이 있긴 한데, 그 내용은 접해보지 않아 정리를 하지 않도록 한다. 차후에 해당 내용에 대해 알게 되면, 다시 정리하도록 하자.
Posted by 강군님

댓글을 달아 주세요