無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

access_ok


access_ok()は参照メモリが、アクセス可能領域かのチェックを行います。返り値が0だとエラーです。実務運用はユーザ空間のメモリを、参照できるかどうかを行います。current_thread_info()->addr_limit.segに参照可能なリミットが設定され、それを超えるメモリは参照できません。システム依存ですが、ユーザー空間は0x00000000-0xbfffffff、カーネル空間は0xc0000000‐0xffffffffです。

従って、current_thread_info()->addr_limit.seg=0xC0000000なら、ユーザー空間メモリで、カーネル空間メモリはエラーとなります。current_thread_info()->addr_limit.seg=0xffffffffで、ユーザ/カーネル空間に関係なく全領域のメモリはOKです。

デフォルトではプロセスは、current_thread_info()->addr_limit.seg=0xC0000000とし、ユーザ空間メモリとやり取りするデバイスドライバ等は、カーネル空間アドレスでコールする事による脆弱性が発生する故、そのような関数には、access_ok()により、ユーザ空間のメモリかどうかチェックを行っています。

なお、lkmからそのような関数をコールする場合、current_thread_info()->addr_limit.seg=0xFFFFFFFFとする事で、カーネル空間メモリを介してのコールが可能となります。

検証サンプル

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

MODULE_LICENSE("GPL");

static int __init hoge_init(void)
{
      char    buf[32];
      mm_segment_t old_fs, a;
      int     ret;

       printk("buff   addr:%p\n", buf);

       old_fs = get_fs();
       ret = access_ok(VERIFY_WRITE, buf, sizeof(buf)) ;
       printk("addr limit1:%lx\n", old_fs.seg);
       printk("ret access1:%d\n", ret);

       set_fs(get_ds());
       a = get_fs();
       ret = access_ok(VERIFY_WRITE, buf, sizeof(buf)) ;
       printk("addr limit2:%lx\n", a.seg);
       printk("ret access2:%d\n", ret);

       return -1;
}

module_init(hoge_init)

実効結果

[root@localhost lkm]# insmod access_ok.ko
insmod: error inserting 'access_ok.ko': -1 Operation not permitted

[root@localhost lkm]# dmesg
  :
[ 4247.615172] buff   addr:dde77ec0
[ 4247.615177] addr limit1:c0000000
[ 4247.615179] ret access1:0           <-アクセス不可
[ 4247.615181] addr limit2:ffffffff
[ 4247.615183] ret access2:1           <-アクセス可

実装

アーキテクチャ依存で、以下はx86での実装です。access_ok()が0ならエラーですが、実態は__range_not_ok(addr, size) == 0とし、__range_not_ok()が0でないならエラーとなります。
typedef struct {
       unsigned long           seg;
} mm_segment_t;

#define PAGE_OFFSET             ((unsigned long)__PAGE_OFFSET)
#define __PAGE_OFFSET           _AC(CONFIG_PAGE_OFFSET, UL)
#define CONFIG_PAGE_OFFSET      0xC0000000
#define _AC(X,Y)                X

#define TASK_SIZE               PAGE_OFFSET
#define TASK_SIZE_MAX           TASK_SIZE
 
#define MAKE_MM_SEG(s)  ((mm_segment_t) { (s) })

#define KERNEL_DS       MAKE_MM_SEG(-1UL)
#define USER_DS         MAKE_MM_SEG(TASK_SIZE_MAX)

#define get_ds()        (KERNEL_DS)
#define get_fs()        (current_thread_info()->addr_limit)
#define set_fs(x)       (current_thread_info()->addr_limit = (x))

#define set_fs(x)       (current_thread_info()->addr_limit = (x))

#define access_ok(type, addr, size) (likely(__range_not_ok(addr, size) == 0))

#define __range_not_ok(addr, size)                                      \
({                                                                      \
       unsigned long flag, roksum;                                     \
       __chk_user_ptr(addr);                                           \
       asm("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0"             \
           : "=&r" (flag), "=r" (roksum)                               \
           : "1" (addr), "g" ((long)(size)),                           \
             "rm" (current_thread_info()->addr_limit.seg));            \
       flag;                                                           \
})
__range_not_okの拡張インラインアセンブラの展開は以下の通りです。sbbはキャリアフラグも考慮したsubで、sbb %0,%0は、flag=0ですが、addr+sizeが32/64ビット(システム依存)をオーバフロしていれば、flag=-1で、sbb $0,%0はcurrent_thread_info()->addr_limit.seを超えていれば、flag - 0 - 1となります。問題なければflag=0で、そうでないならflag=-1/-2となります。
"1" (addr): roksum = addr
add %3,%1 : roksum = roksum + size;
sbb %0,%0 : flag   = flag - flag - CF
cmp %1,%4 : roksum < current_thread_info()->addr_limit.seg
sbb $0,%0 : flag   = flag - 0 - CF

追記

__chk_user_ptr()は、__CHECKER__なら、externとし、そうでないなら(void)0とします。これはコンパイル時、インラインのaccess_okの引数が__userとなっているかのチェック故かと思います。extern void __chk_user_ptr()は、実装されておらず、推測ですが、parseコマンドで__chk_user_ptr()を削除するか、retのみのr__chk_user_ptrのシンボルとして実装するかと思います。たぶん削除されるのでは。と思いますが・・・。(ソース内に関数/シンボルセクションでの__chk_user_ptr掛かる実装は無いようです。)
#ifdef __CHECKER__
 # define __user         __attribute__((noderef, address_space(1)))
 # define __kernel       __attribute__((address_space(0)))
 # define __safe         __attribute__((safe))
 # define __force        __attribute__((force))
 # define __nocast       __attribute__((nocast))
 # define __iomem        __attribute__((noderef, address_space(2)))
   :
 extern void __chk_user_ptr(const volatile void __user *);
 extern void __chk_io_ptr(const volatile void __iomem *);
#else
 # define __user
 # define __kernel
 # define __iomem
 # define __chk_user_ptr(x) (void)0
 # define __chk_io_ptr(x) (void)0
  :
#endif


最終更新 2015/10/25 18:26:34 - north
(2015/10/25 18:26:34 作成)