strncpy_from_user()を読んで見る

なんとなくLinuxカーネルのライブラリにあるstrncpy_from_user()を読んでみます。

ファイルはlib/strncpy_from_user.cです。

関数名から何をするかは想像つきますね。関数はこうなってます。

 99 long strncpy_from_user(char *dst, const char __user *src, long count)
100 {
101         unsigned long max_addr, src_addr;
102 
103         if (unlikely(count <= 0))
104                 return 0;
105 
106         max_addr = user_addr_max();
107         src_addr = (unsigned long)src;
108         if (likely(src_addr < max_addr)) {
109                 unsigned long max = max_addr - src_addr;
110                 return do_strncpy_from_user(dst, src, count, max);
111         }
112         return -EFAULT;
113 }
114 EXPORT_SYMBOL(strncpy_from_user);

最初にアドレスのチェックがあります。まず、max_addrは何になるのかということでuser_addr_max()を見てみます。arch/x86/include/asm/uaccess.hでこのようなマクロとして定義されています。

36 #define user_addr_max() (current_thread_info()->addr_limit.seg)

segはmm_segment_t構造体のメンバ変数です。この構造体はarch/x86/include/asm/processor.hで定義されています。

486 typedef struct {
487         unsigned long           seg;
488 } mm_segment_t;

で、実際どんな値が入っているのかを↓だけ実行するカーネルモジュールを作って見てみると、

        pr_info("limit -> 0x%lx\n", current_thread_info()->addr_limit.seg);

このようになります。

[ 6494.031450] limit -> 0x7ffffffff000

linuxx86_64の場合、ユーザランドアドレス空間Documentation/x86/x86_64/mm.txtにて下記のようになると書かれています。

  6 0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm

そして、うちの環境では1ページは4KiBなので、0x7ffffffff000はユーザランドのアドレス-1ページ分という感じですね

masami@saga:~$ getconf PAGE_SIZE
4096

ということで最初のチェックは、コピー元のアドレスが変な場所にないかをチェックしています。

実際に文字列をコピーするのはdo_strncpy_from_user()ですね。

4番目のmaxはunsigned long max = max_addr - src_addr;で決まった値になります。

23 static inline long do_strncpy_from_user(char *dst, const char __user *src, long count, unsigned long max)

最初にコピーするサイズを決めます。

 24 {
 25         const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
 26         long res = 0;
 27 
 28         /*
 29          * Truncate 'max' to the user-specified limit, so that
 30          * we only have one limit we need to check in the loop
 31          */
 32         if (max > count)
 33                 max = count;
 34 

アドレスがsizeof(long) - 1の境界にアライメントされてるかのチェックです。これはCONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESSがyならチェックされます。多分大抵のディストリビューションならyになってるんじゃないかと。longの境界に無い場合はbyte_at_a_timeラベルに飛びます。

 35         if (IS_UNALIGNED(src, dst))
 36                 goto byte_at_a_time;
 37 

ここからlongのサイズごとにコピーが行われます。

 38         while (max >= sizeof(unsigned long)) {
 39                 unsigned long c, data;
 40 
 41                 /* Fall back to byte-at-a-time if we get a page fault */
 42                 if (unlikely(__get_user(c,(unsigned long __user *)(src+res))))
 43                         break;
 44                 *(unsigned long *)(dst+res) = c;

has_zero()はarch/x86/include/asm/word-at-a-time.hにあります。cに0が含まれている場合に0以外の値が返ります。なのでhas_zero()が真になる場合はNULL文字が含まれている場合ということなので、コピーがここで終了となりますね。

 45                 if (has_zero(c, &data, &constants)) {
 46                         data = prep_zero_mask(c, data, &constants);
 47                         data = create_zero_mask(data);
 48                         return res + find_zero(data);
 49                 }
 50                 res += sizeof(unsigned long);
 51                 max -= sizeof(unsigned long);
 52         }
 53 

byte_at_a_timeラベルのほうは素直に1バイトずつのコピーです。

 54 byte_at_a_time:
 55         while (max) {
 56                 char c;
 57 
 58                 if (unlikely(__get_user(c,src+res)))
 59                         return -EFAULT;
 60                 dst[res] = c;
 61                 if (!c)
 62                         return res;
 63                 res++;
 64                 max--;
 65         }
 66 
 67         /*
 68          * Uhhuh. We hit 'max'. But was that the user-specified maximum
 69          * too? If so, that's ok - we got as much as the user asked for.
 70          */
 71         if (res >= count)
 72                 return res;
 73 
 74         /*
 75          * Nope: we hit the address space limit, and we still had more
 76          * characters the caller would have wanted. That's an EFAULT.
 77          */
 78         return -EFAULT;
 79 }

ちなみに、マジックワードの定番ともいえる0xdeadbeefinclude/linux/kernel.hにあります。

45 #define STACK_MAGIC     0xdeadbeef