今日もひたすらアクセスベクタの解析。
ほとんど自分用メモ。

ネットワークとプロセス間通信の制御でSELinuxができること

このところの解析のまとめ。

  • TCP/IP通信
    • ドメイン間通信の制御は(直接の形では)できない
    • ソケットの利用許可・不許可自体の制御は可能
      • 「ソケットのreadの利用許可,不許可」はできるが,相手ドメインを指定して許可不許可はできないということ
    • ポート番号,ネットワークアドレス,NICを指定したアクセス制御は可能。
      • ポート番号:送受信,ポートで接続待ち,接続開始を制御可能
      • ネットワークアドレス,NIC:送受信を制御可能。
        • このへんをうまく組み合わせてやれば,ドメイン間通信の制御をある程度代用できそう。
  • UNIXドメインソケット通信
    • sendto, connecttoのように「接続を開始する」ことの制御のみ可能。
  • IPC
    • IPCオブジェクトの生成,消去,読み書きの制御が可能

SELinux Policy Editorでは,TCP/IP通信の制御を抜本的に見直す必要がありそう。
たぶん来年だな…
今のSELinuxのstrictポリシでは,ネットワークのアクセス制御設定を細かく行うのは玄人でも難しい。マクロが用意されて無いので。
その割に,「簡単」をうたっているセキュアOSでは,この辺のアクセス制御が結構できそう。
なので,これを簡単に行えるようにするだけでも,SELinux Policy Editorの存在意義が上がるはず。

LinuxケーパビリティとSELinuxケーパビリティの関係

SELinuxのcapabilityの解析。
Linuxのソースをみると,ケーパビリティ(capability)のチェックは,「capable」関数を使って行っている。

●linux/sched.h
#ifdef CONFIG_SECURITY
/* code is in security.c */
extern int capable(int cap);
#else
static inline int capable(int cap)
{
        if (cap_raised(current->cap_effective, cap)) {
                current->flags |= PF_SUPERPRIV;
                return 1;
        }
        return 0;
}
#endif

security.c(LSM)を使っている時は,security.cを見ろと書いてある。

● security/security.c
int capable(int cap)
{

        if (security_ops->capable(current, cap)) {★
                /* capability denied */
                return 0;
        }

        /* capability granted */
        current->flags |= PF_SUPERPRIV;
        return 1;
}

LSMを使っている時は,★で,LSMのフックが呼ばれるようだ。
SELinuxを使っている場合は,以下のフックが呼ばれる。

static int selinux_capable(struct task_struct *tsk, int cap)
{
        int rc;

        rc = secondary_ops->capable(tsk, cap);★
        if (rc)
                return rc;

        return task_has_capability(tsk,cap);★★
}

secondary_opsには,普通はLinux capablityが入っている(security/capability.c)はずなので,★では,普通のLinux capabilityのチェックがされる。
★★にて,SELinuxのcapablityのチェックがされる。
つまり,ドメインがcapabilityを持ってるのかのチェックがされる。
確かに,この実装だと,capabilityの意味は普通のLinuxと互換性あると思ったが,
SELinuxのフック(hooks.c)内部でも「capable」が使われてる…
capabilityの詳細な意味の解析は後日にしよう…

また,capability関連のログがenforcingモードでしか出ないことがあったが,
この理由は不明。

タイプがソケットを作成したドメインになるもの

id:himainu:20051017で,一つ忘れてた。
ソケット関連のアクセスベクタで,「socket_has_perm」を使ってチェックしているものについては,ドメイン間通信の制御ができない。
read, write, create, getattr, bind, connect, listen, accep, getopt, setopt, shutdown
が該当する。
アクセス制御されるときのタイプは,ソケットを作成したドメインになる。

具体例

で,具体的に,
read, write, create, getattr, bind, connect, listen, accep, getopt, setopt, shutdown
を使って何ができるか。

ドメインa_t→b_tのTCP通信を例にして(少し簡略化してる)
1) a_tがTCPソケット作成。
ソケット(正確にはソケットのinode)のタイプは「a_t」。

2)a_tがb_tとconnectionを張ろうとする
ドメイン:a_t,
タイプ:a_t
オブジェクトクラスtcp_socket
アクセスベクタconnect
のチェックが走る
これを許可するには
allow a_t a_t:tcp_socket connect;
が必要。
allow a_t b_tではない!!

もう一つの例(あまりない例)
1) a_tがTCPソケット作成。
ソケット(正確にはソケットのinode)のタイプは「a_t」。

2)a_tがプロセスを生成,ドメイン遷移しc_tになる。
1)のソケットを継承する。そのタイプはa_t。

3)c_tがb_tとconnectionを張ろうとする
ドメイン:c_t,
タイプ:a_t
オブジェクトクラスtcp_socket
アクセスベクタconnect
のチェックが走る
これを許可するには
allow c_t a_t:tcp_socket connect;
が必要。

上のソースコード解析

これは分かりにくかった(アクセスベクタのgrepではすまなかった)。
ソースコードをちゃんと解析する必要があった。忘れそうだから載せとこう。

ソケットのrecvmsgの際のフックを例にして解析。

static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg,
                                  int size, int flags)
{
        return socket_has_perm(current, sock, SOCKET__READ);
}

socket_has_permが呼ばれる。

static int socket_has_perm(struct task_struct *task, struct socket *sock,
                           u32 perms)
     struct inode_security_struct *isec;
        struct task_security_struct *tsec;
        struct avc_audit_data ad;
        int err = 0;

        tsec = task->security;
        isec = SOCK_INODE(sock)->i_security;

        if (isec->sid == SECINITSID_KERNEL)
                goto out;

        AVC_AUDIT_DATA_INIT(&ad,NET);
        ad.u.net.sk = sock->sk;
        err = avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad);
	★↑ここでパーミッションチェック
…
err = avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad);

ドメイン(tsec->sid)はプロセスのドメイン
問題は,タイプ(isec->sid)が何か。
isec = SOCK_INODE(sock)->i_security;
となっている。
ソケット構造体のiノードに付与されているタイプらしい。

次の問題は,ソケット構造体のiノードにどんなタイプが付与されるか。
ソケットのiノードのタイプ初期化は以下で行われる。

static void selinux_socket_post_create(struct socket *sock, int family,
                                       int type, int protocol, int kern)
{
        struct inode_security_struct *isec;
        struct task_security_struct *tsec;

        isec = SOCK_INODE(sock)->i_security;

        tsec = current->security;
        isec->sclass = socket_type_to_security_class(family, type, protocol);
        isec->sid = kern ? SECINITSID_KERNEL : tsec->sid;  ★
        isec->initialized = 1;

        return;
}

★が鍵。kernの値が気になる。
ユーザランドから使う時は,「kern」は「0」。
net/socket.c

int sock_create(int family, int type, int protocol, struct socket **res)
{
        return __sock_create(family, type, protocol, res, 0(これが「kern」に渡る));
}

のように,「kern」が「0」でOK。kernが1になるのはカーネルが内部的に使う時のみ。
で,
isec->sid = kern ? SECINITSID_KERNEL : tsec->sid;
では,tsec->sid、「つまりソケットを作成したドメイン」が使われる。

結局,
err = avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad);
では,ドメイン,ソケットを作成したドメイン,をもとにアクセス制御が行われることが分かる。