kernel メッセージのTaintedについて

kernelのメッセージが出た時にいつもNot taintedって出てましたが、このまえ、初めて、Taintedって出ていたのでメモしました。

これまでは、以下のようなNot taintedってメッセージ。Notって書いてあったのであまり気にしていませんでした。

Apr 22 16:48:27 dev001 kernel: Pid: 2429, comm: perl Not tainted 2.6.32-431.el6.x86_64 #1

それがこんな感じのメッセージ出ていたのでちょっとびっくり。

(Tainted: G        W  -- ------------   )

調べてみたところ、フラグの意味は以下のような感じみたいです。

/**
 *      print_tainted - return a string to represent the kernel taint state.
 *
 *  'P' - Proprietary module has been loaded.
 *  'F' - Module has been forcibly loaded.
 *  'S' - SMP with CPUs not designed for SMP.
 *  'R' - User forced a module unload.
 *  'M' - System experienced a machine check exception.
 *  'B' - System has hit bad_page.
 *  'U' - Userspace-defined naughtiness.
 *  'D' - Kernel has oopsed before
 *  'A' - ACPI table overridden.
 *  'W' - Taint on warning.
 *  'C' - modules from drivers/staging are loaded.
 *  'I' - Working around severe firmware bug.
 *  'O' - Out-of-tree module has been loaded.
 *  'E' - Unsigned module has been loaded.
 *  'L' - A soft lockup has previously occurred.
 *  'K' - Kernel has been live patched.
 *
 *      The string is overwritten by the next call to print_tainted().
 */

http://askubuntu.com/questions/248470/what-does-the-kernel-taint-value-mean

あと、procからtaintedのstatusも確認できました。 何もなければ、0なのですが、warningが出ている状態だと、512が返ってきていました。

cat /proc/sys/kernel/tainted

statusの意味は以下のとおり。

tainted:

Non-zero if the kernel has been tainted.  Numeric values, which
can be ORed together:

   1 - A module with a non-GPL license has been loaded, this
       includes modules with no license.
       Set by modutils >= 2.4.9 and module-init-tools.
   2 - A module was force loaded by insmod -f.
       Set by modutils >= 2.4.9 and module-init-tools.
   4 - Unsafe SMP processors: SMP with CPUs not designed for SMP.
   8 - A module was forcibly unloaded from the system by rmmod -f.
  16 - A hardware machine check error occurred on the system.
  32 - A bad page was discovered on the system.
  64 - The user has asked that the system be marked "tainted".  This
       could be because they are running software that directly modifies
       the hardware, or for other reasons.
 128 - The system has died.
 256 - The ACPI DSDT has been overridden with one supplied by the user
        instead of using the one provided by the hardware.
 512 - A kernel warning has occurred.
1024 - A module from drivers/staging was loaded.
2048 - The system is working around a severe firmware bug.
4096 - An out-of-tree module has been loaded.
8192 - An unsigned module has been loaded in a kernel supporting module
       signature.
16384 - A soft lockup has previously occurred on the system.
32768 - The kernel has been live patched.

https://www.kernel.org/doc/Documentation/sysctl/kernel.txt

taintedという響きから嫌だなぁと思っていましたが、同じtaintedでも中身によって掘り下げれそうですね。

smapsみてみる

Linuxのメモリ周りをみていて、手元でもsmapsの内容を見てみました。 環境はkernel 3.10.0-229.el7.x86_64(バージョンアップしなきゃ。。)

実行したテストプログラムは以下のブログから丸々コピーさせてもらいました。

Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる - naoyaのはてなダイアリー

1回目がforkするだけのテストプログラムを動かした結果で、 2回目が、ブログにあるとおり、コメントアウトを外して、fork & 書き込みまでおこなった結果です。 ほぼ参考にさせて頂いたブログにあるとおりの結果になっていました。

# 1回目
00602000-067aa000 rw-p 00000000 00:00 0
Size:             100000 kB
Rss:              100000 kB
Pss:               50000 kB
Shared_Clean:          0 kB
Shared_Dirty:     100000 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:       100000 kB
Anonymous:        100000 kB
AnonHugePages:     96256 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac
#2回目
00602000-067aa000 rw-p 00000000 00:00 0
Size:             100000 kB
Rss:              100000 kB
Pss:               75596 kB
Shared_Clean:          0 kB
Shared_Dirty:      48808 kB
Private_Clean:         0 kB
Private_Dirty:     51192 kB
Referenced:       100000 kB
Anonymous:        100000 kB
AnonHugePages:     96256 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac

このPssなる項目は、proportional set sizeというものらしいです。 以下のサイトに例がのっていてなんとなくわかったような気になりました。(実際はメモリ周りの扱いがわかっていないので、わかっていない。。。)

The "proportional set size" (PSS) of a process is the count of pages it has
in memory, where each page is divided by the number of processes sharing it.
So if a process has 1000 pages all to itself, and 1000 shared with one other
process, its PSS will be 1500.

https://www.kernel.org/doc/Documentation/filesystems/proc.txt

今回のテストの場合もprivateとsharedが1:1なので、PSSの値もほぼ上記の例と同じ比率になっているような気がします。

詳しくはまたどこかで調べてみます。

dirname()とbasename()の挙動の確認

現在、社内の人と勉強会をしているLinuxProgramingInterfaceにて出て来たdirname()とbasename()の挙動について、実際に確認してみました。

18章に以下のような文言がありました。

dirname() と basename() はいずれも pathname の文字列を変更する可能性があります。  
このため、pathname を再度利用する場合は、例 18-5 に挙げるようにコピーを作成し、コピーを dirname()、basename() へ渡します。

ここでいうpathnameはdirname() or basename()に与える/xxx/yyyみたいなやつです。

上記がどういうことかと教えてもらったところ、両関数は与えられた文字列を直接書き換えちゃうので、もっかい文字列を参照したい場合はコピーした文字列を両関数に渡してねってことらしいです。

本当に文字列を書き換えてしまうのか確認してみました。

使用したソースコードは、LinuxProgramingInterfaceの中に出て行きた例18-5です。

http://man7.org/tlpi/code/online/dist/dirs_links/t_dirbasename.c.html

gdbで見てみます。
下記は、行数の確認 & 引数の指定 & 引数の確認 & ブレイクポイントの設定です。

(gdb) list
23  #include <libgen.h>
24  #include "tlpi_hdr.h"
25
26  int
27  main(int argc, char *argv[])
28  {
29      char *t1, *t2;
30      int j;
31
32      for (j = 1; j < argc; j++)  {
(gdb) set args /dirname/filename
(gdb) show args
Argument list to give program being debugged when it is started is "/dirname/filename".
(gdb) b 30

実行!
与える文字列は/dirname/filenameとしました。
文字列をt1,t2にコピーして、中身を確認します。
この時点ではt1,t2ともに/dirname/filenameのままみたいです。

(gdb) r
Starting program: /xxxx/t_dirbasename /dirname/filename

Breakpoint 1, main (argc=2, argv=0x7fffffffe648) at t_dirbasename.c:32
32      for (j = 1; j < argc; j++)  {
(gdb) n
33          t1 = strdup(argv[j]);
(gdb) n
34          if (t1 == NULL)
(gdb) n
36          t2 = strdup(argv[j]);
(gdb) n
37          if (t2 == NULL)
(gdb) n
40          printf("%s ==> %s + %s\n", argv[j], dirname(t1), basename(t2));
(gdb) p t1
$1 = 0x603010 "/dirname/filename"
(gdb) p t2
$2 = 0x603030 "/dirname/filename"
(gdb) p *t1
$3 = 47 '/'
(gdb) p *t2
$4 = 47 '/'
(gdb) p *(t1+1)
$5 = 100 'd'
(gdb) p *(t1+2)
$6 = 105 'i'
(gdb) p *(t1+3)
$7 = 114 'r'
(gdb) p *(t1+4)
$8 = 110 'n'
(gdb) p *(t1+5)
$9 = 97 'a'
(gdb) p *(t1+6)
$10 = 109 'm'
(gdb) p *(t1+7)
$11 = 101 'e'
(gdb) p *(t1+8)
$12 = 47 '/'
(gdb) p *(t1+9)
$13 = 102 'f'

さて、問題のdirname(),basename()の実行。
確かにt1は/dirnameになり、t2は/dirname/filenameになりました。
着目すべきは*(t1+8)が /ではなく、\000に書き換えられているところですね! だからt1が/dirnameになるんですね〜。

(gdb) n
/dirname/filename ==> /dirname + filename
42          free(t1);
(gdb) p t1
$14 = 0x603010 "/dirname"
(gdb) p t2
$15 = 0x603030 "/dirname/filename"
(gdb) p *t1
$16 = 47 '/'
(gdb) p *t2
$17 = 47 '/'
(gdb) p *(t1+8)
$18 = 0 '\000'
(gdb) p *(t1+9)
$19 = 102 'f'

もちろん、p t1+9は、filenameになりました。

(gdb) p *(t2+1)
$20 = 100 'd'
(gdb) p t1+9
$21 = 0x603019 "filename"
(gdb) quit

以上、確認終わり!

gdbでスタックフレームを見てみる

最近Linuxプログラミングインターフェースの輪読をおこなっているので、その復習のため、ちょっと見てみました。

こんなソースを実行。 allocaはおまけです。

     1   #include <stdio.h>
     2  #include <alloca.h>
     3
     4  void test1(int *);
     5  void test2(int *);
     6  void test3(int *);
     7
     8  int main(int argc, char *argv) {
     9    int a = 111;
    10    test1(&a);
    11    return 0;
    12  }
    13
    14  void test1(int *a){
    15    printf("test1 : %d\n",*a);
    16    printf("test1 address : %p\n",test1);
    17    test2(a);
    18  }
    19
    20  void test2(int *a){
    21    *a = 222;
    22    void *x;
    23    printf("test2 : %d\n",*a);
    24    printf("test2 address : %p\n",test2);
    25    x = alloca(4);
    26    printf("alloca : %p\n",x);
    27    test3(a);
    28  }
    29
    30  void test3(int *a){
    31    *a = 333;
    32    printf("test3 : %d\n",*a);
    33    printf("test3 address : %p\n",test3);
    34  }

gdbでdebugしてみる。 break pointの設定 && 実行

(gdb) break 34
Breakpoint 1 at 0x400678: file ./stack_test.c, line 34.
(gdb) run
Starting program: /home/foxtrot/stack_test/./a.out
test1 : 111
test1 address : 0x400559
test2 : 222
test2 address : 0x40059e
alloca : 0x7fffffffe510
test3 : 333
test3 address : 0x400637

Breakpoint 1, test3 (a=0x7fffffffe59c) at ./stack_test.c:34
34  }
(gdb)

backtrace! 先頭からプログラムカウンタの値、関数名、引数、コードの実行箇所みたいです。

(gdb) backtrace
#0  test3 (a=0x7fffffffe59c) at ./stack_test.c:34
#1  0x0000000000400635 in test2 (a=0x7fffffffe59c) at ./stack_test.c:27
#2  0x000000000040059c in test1 (a=0x7fffffffe59c) at ./stack_test.c:17
#3  0x0000000000400552 in main (argc=1, argv=0x7fffffffe688 "\253\350\377\377\377\177") at ./stack_test.c:10

frame 数字 を指定することで、実行しているソースコードの場所も見れます。 このときlistを実行することで、さらにその周りのソースコードも見れるとか。

(gdb) frame 1
#1  0x0000000000400635 in test2 (a=0x7fffffffe59c) at ./stack_test.c:27
27    test3(a);
(gdb) list
22    void *x;
23    printf("test2 : %d\n",*a);
24    printf("test2 address : %p\n",test2);
25    x = alloca(4);
26    printf("alloca : %p\n",x);
27    test3(a);
28  }
29
30  void test3(int *a){
31    *a = 333;

info localsでローカル変数。info argsで引数

(gdb) info args
a = 0x7fffffffe59c
(gdb) info locals
x = 0x7fffffffe510

info frame 数字 でより詳しい情報がみれます。 呼び出し元とか呼び出し先とかプログラムカウンタの値とか。

(gdb) info frame 0
Stack frame at 0x7fffffffe510:
 rip = 0x400678 in test3 (./stack_test.c:34); saved rip 0x400635
 called by frame at 0x7fffffffe560
 source language c.
 Arglist at 0x7fffffffe500, args: a=0x7fffffffe59c
 Locals at 0x7fffffffe500, Previous frame's sp is 0x7fffffffe510
 Saved registers:
  rbp at 0x7fffffffe500, rip at 0x7fffffffe508
(gdb) info frame 1
Stack frame at 0x7fffffffe560:
 rip = 0x400635 in test2 (./stack_test.c:27); saved rip 0x40059c
 called by frame at 0x7fffffffe580, caller of frame at 0x7fffffffe510
 source language c.
 Arglist at 0x7fffffffe550, args: a=0x7fffffffe59c
 Locals at 0x7fffffffe550, Previous frame's sp is 0x7fffffffe560
 Saved registers:
  rbp at 0x7fffffffe550, rip at 0x7fffffffe558
(gdb) info frame 2
Stack frame at 0x7fffffffe580:
 rip = 0x40059c in test1 (./stack_test.c:17); saved rip 0x400552
 called by frame at 0x7fffffffe5b0, caller of frame at 0x7fffffffe560
 source language c.
 Arglist at 0x7fffffffe570, args: a=0x7fffffffe59c
 Locals at 0x7fffffffe570, Previous frame's sp is 0x7fffffffe580
 Saved registers:
  rbp at 0x7fffffffe570, rip at 0x7fffffffe578
(gdb) info frame 3
Stack frame at 0x7fffffffe5b0:
 rip = 0x400552 in main (./stack_test.c:10); saved rip 0x7ffff7a3bb15
 caller of frame at 0x7fffffffe580
 source language c.
 Arglist at 0x7fffffffe5a0, args: argc=1, argv=0x7fffffffe688 "\253\350\377\377\377\177"
 Locals at 0x7fffffffe5a0, Previous frame's sp is 0x7fffffffe5b0
 Saved registers:
  rbp at 0x7fffffffe5a0, rip at 0x7fffffffe5a8

Stack frame at xxxxの箇所が、Stackのメモリアドレスなのかな。 試しにallocaに大きな値を入れてみた。

25    x = alloca(64);

これでgdbで再度見てみると確かに値が変わっている! 前回はframe 0の値が「Stack frame at 0x7fffffffe510:」だったけれども、今回は「Stack frame at 0x7fffffffe4e0:」になっている。

(gdb) info frame 0
Stack frame at 0x7fffffffe4e0:
 rip = 0x4006c2 in test3 (./stack_test.c:37); saved rip 0x400635
 called by frame at 0x7fffffffe560
 source language c.
 Arglist at 0x7fffffffe4d0, args: a=0x7fffffffe59c
 Locals at 0x7fffffffe4d0, Previous frame's sp is 0x7fffffffe4e0
 Saved registers:
  rbp at 0x7fffffffe4d0, rip at 0x7fffffffe4d8
(gdb) info frame 1
Stack frame at 0x7fffffffe560:
 rip = 0x400635 in test2 (./stack_test.c:27); saved rip 0x40059c
 called by frame at 0x7fffffffe580, caller of frame at 0x7fffffffe4e0
 source language c.
 Arglist at 0x7fffffffe550, args: a=0x7fffffffe59c
 Locals at 0x7fffffffe550, Previous frame's sp is 0x7fffffffe560
 Saved registers:
  rbp at 0x7fffffffe550, rip at 0x7fffffffe558

ripとかは退避したプログラムカウンタの値?ripはeipの64bit版なんですかね。。 なんか調べると奥が深そうだから、また今度!

lldbコマンドの使い方

もう完全に忘れていたので、メモ!

gccコンパイルするときに-gオプションをつける。

gcc -g ./test.c

こんな感じで起動

lldb ./a.out

listでソースコードをみれる。breakpointを設定したい行番号を確認。

(lldb) list
   7    int main(void){
   8        int a,b,i=0,j=0;
   9        int checkresult;
   10       char tmp[5];
...

ちなみにenterをおしたら前と同じコマンドの繰り返し。

breakpointを設定。breakpointの"b"と"ソースコードのファイル名":"行番号"

(lldb) b test.c:25

breakpointを設定後、rで実行。

(lldb) r
Process 2604 launched: './a.out' (x86_64)

sで1行ずつ実行。nでも進む。nだと関数とかは飛ばしてくれる。

(lldb) s
   23           M[j][strlen(M[j]) - 1] = '\0';
   24       }
   25       for(i=0;i<a;i++){
-> 26            for(j=0;j<a;j++){

"print 変数名"で実行しているタイミングの変数の値が確認できる

(lldb) print i
(int) $6 = 0
(lldb) print N[0]
(char [201]) $8 = "0000"

なんかちょっとポインタの勉強になっておもしろい。

(lldb) print N
(char (*)[201]) $10 = 0x00007fff5fbf5c78
(lldb) print *N
(char [201]) $11 = "0000"
(lldb) print *N[0]
(char) $12 = '0'

quitでおしまい。

(lldb) quit
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] Y

ファイルへの追記のみを可能にする方法

知らなかったので、メモ。 logファイルの改ざん防止とか良さそう。

準備。lsattrで現在のファイルの属性をみれる。

[root@test foxtrot]# touch addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# lsattr addonly.txt
-------------e- addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# echo "first" > addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# cat addonly.txt
first
[root@test foxtrot]#

追加のみ許可の属性を付与。 上書きしたら、メッセージが表示されてエラー。 追記はOK!

[root@test foxtrot]# chattr +a addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# lsattr addonly.txt
-----a-------e- addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# echo "second" > addonly.txt
bash: addonly.txt: 許可されていない操作です
[root@test foxtrot]#
[root@test foxtrot]# echo "second" >> addonly.txt
[root@test foxtrot]#
[root@test foxtrot]# cat addonly.txt
first
second

まぁ、logファイルを改ざんできるぐらいなら、、、とかも思うけど。

CentOS7でunboundのrpmを作る

いま借りてるCent7でunboundをyum installすると、1.4系が入ってくる。 これは脆弱性がある古いバージョンらしいので、1.5系が欲しい。 CentOS6ならば以下のFedoraのepelに1.5系のrpmがあったんだけど、CentOS7はなさそう。

http://dl.fedoraproject.org/pub/epel/6/x86_64/unbound-1.5.1-1.el6.x86_64.rpm

unboundの最新版が欲しくて、以下のサイトを参考にrpmを作ってみることにした。

heartbeats.jp

上記のURLとは対応バージョンが異なるので、バージョンの記載だけ変更する。

rpmbuildをすると以下のエラーが。

checking for libevent... configure: error: Cannot find the libevent library in /usr/local /opt/local /usr/lib /usr/pkg /usr/sfw /usr
You can restart ./configure --with-libevent=no to use a builtin alternative.
Please note that this alternative is not as capable as libevent when using
large outgoing port ranges.
エラー: /var/tmp/rpm-tmp.ud07R2 の不正な終了ステータス (%prep)

libeventは/usr/lib64にあったんだけど、そこをみてくれていないみたい。 rpmbuildの変数とかは大丈夫そうなんだけど。。。

[foxtrot@test src]$ rpmbuild --showrc | grep 64
〜
-14: _libdir    %{_prefix}/lib64
〜

configureをみてみると以下の記載が。。

  --with-libevent=pathname
                          use libevent (will check /usr/local /opt/local
                          /usr/lib /usr/pkg /usr/sfw /usr or you can specify
                          an explicit path). Slower, but allows use of large
                          outgoing port ranges.

pathnameをspecファイルに追加してあげればよいのかと、以下のように変更した

--with-libevent=/usr/lib64

けれどもいっこうにエラーは解消せず、同じメッセージばかり。pathの書き方をいろいろ変えてみたけど、同じだった。 仕方なく力技で/usr/lib配下にシンボリックリンクを作ってあげたら、通るようになった。

sudo ln -s /usr/lib64/libevent-2.0.so.5 /usr/lib/libevent

rpmファイルはできたけど、果たしてこれで動くのやら。

[root@test unbound-1.5.7]# ls /root/rpmbuild/RPMS/x86_64/unbound*
/root/rpmbuild/RPMS/x86_64/unbound-1.5.7-1.el7.centos.x86_64.rpm  /root/rpmbuild/RPMS/x86_64/unbound-debuginfo-1.5.7-1.el7.centos.x86_64.rpm

明日以降頑張ってみる。