fs.protected_symlinks（シンボリックリンクの保護）について

最近のLinuxではシンボリックリンクに制限が加えられている。知らなかった。

シンボリックリンクとは、Windowsのショートカットのようなものである。ただし、ファイルシステムレベルで実装されているので、アプリ側は何もしなくても、シンボリックリンクを開くとリンク先のファイルが開かれる。

例えば、最近RedHat系のLinuxのパッケージ管理がyumからdnfになった。「yumと打っても動くように、/usr/bin/dnfを/usr/bin/yumをコピーしておこうかな」というときに、リンク先が/usr/bin/dnfの/usr/bin/yumというシンボリックリンクを作っておくと、ディスク容量を消費することなく目的が達成できる。

Linuxでは誰でもシンボリックリンクを作れる上に、アプリ側は何もしなくても……というか特別に対処をしないとリンク先のファイルを開いてしまうので、脆弱性になりがち。

[7\-1\. シンボリックリンクの悪用](https://www.ipa.go.jp/security/awareness/vendor/programmingv1/b07_01.html)

脆弱性を作り込まないのがとても大変。というか無理では。このIPAのページにも完璧に対処したコードは載っていないし、「であろう」で締められている。

> （注３・実際にはrealpath( )ライブラリ関数の呼び出しとsafecreat( )関数の呼び出しの間にわずかな時間間隔が存在するためレースコンディションが発生する。さらにrealpath( )ライブラリ関数自体の実装でもlstat( )を繰り返し行い，シンボリックリンクかどうか判断しているので，やはりレースコンディションが発生する。レースコンディションを排除する完璧な実装は困難である。妥当な設計としては，ファイルパスを構成するすべてのディレクトリのパーミッションをチェックし，スーパユーザまたはアプリケーションの実行ユーザのみに書き込み権限があることを確認し，さらにシンボリックリンクでないことを確認する，という手法であろう。）

ということで（？）Linuxに緩和策が追加されていた。

```shell-session
[root@tk2-239-29470 ~]# docker run --rm -it centos:8
[root@a327c9f3009e /]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="8"
 :
[root@a327c9f3009e /]# adduser user
[root@a327c9f3009e /]# su user
[user@a327c9f3009e /]$ cd /tmp/
[user@a327c9f3009e tmp]$ echo "hello" > target
[user@a327c9f3009e tmp]$ ln -s target symbolic_link
[user@a327c9f3009e tmp]$ ls -al
total 32
drwxrwxrwt 1 root root 4096 Feb 25 13:47 .
 :
lrwxrwxrwx 1 user user    6 Feb 25 13:47 symbolic_link -> target
-rw-rw-r-- 1 user user    6 Feb 25 13:47 target
```

適当にユーザーを作り、リンク先が/tmp/targetの/tmp/symbolic_linkというシンボリックリンクを作成。

```shell-session
[user@a327c9f3009e tmp]$ cat symbolic_link
hello
[user@a327c9f3009e tmp]$ echo "world" >> symbolic_link
[user@a327c9f3009e tmp]$ cat symbolic_link
hello
world
[user@a327c9f3009e tmp]$ cat target
hello
world
```

symbolic_linkを読むとtargetの内容が読み出されるし、symbolic_linkに書き込むとtargetに書き込まれる。これがシンボリックリンクである。

ここで、rootに戻ってsymbolic_linkを読み書きしようとしてみると……、

```shell-session
[user@a327c9f3009e tmp]$ exit
exit
[root@a327c9f3009e /]# cd /tmp/
[root@a327c9f3009e tmp]# cat symbolic_link
cat: symbolic_link: Permission denied
[root@a327c9f3009e tmp]# echo "aaaa" >> symbolic_link
bash: symbolic_link: Permission denied
```

失敗する。これが緩和策。rootでも`Permission denied`になることがあるんだ。詳細はここに書かれている。

https://www.kernel.org/doc/Documentation/sysctl/fs.txt#:~:text=symlinks%20are%20permitted%20to%20be%20followed

> protected_symlinks:
> :
> When set to "1" symlinks are permitted to be followed only when outside a sticky world-writable directory, or when the uid of the symlink and follower match, or when the directory owner matches the symlink's owner.

* Stickyでworld-writableなディレクトリにあるシンボリックを辿るのは禁止される
    * でも、シンボリックリンクを作ったユーザーならOK
    * ディレクトリの所有者が作ったシンボリックリンクもOK

と読んだほうが分かりやすいか。

Stickyというのはsticky bitのことで、これがディレクトリについていると、書き込みが可能なディレクトリでも、その中のファイルの削除や名前の変更はファイルの所有者しかできなくなる（普通はファイルの所有者でなくてもディレクトリの書き込み権限を持っていれば削除できる）。これとシンボリックリンクの制限を直接紐付ける理由は分からないので、stickyでworld-writableな/tmpや/var/tmpを対象にしたいということなのだと思う。最後も/tmpの所有者はrootなので、rootが作ったシンボリックリンクはOKということにしたいのだと思う。

一時的に無効にしたければ、

```shell-session
sysctl fs.protected_symlinks=0
```

恒久的に無効にしたければ、どこかに設定ファイルがあるはずで、それを書き換える。CentOS 8ならば、

```/usr/lib/sysctl.d/50-default.conf
 :
# To override settings in this file, create a local file in /etc
# (e.g. /etc/sysctl.d/90-override.conf), and put any assignments
# there.
 :
# Enable hard and soft link protection
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
```

注意書きにあるように、90-override.confを書き換えれば良いのかな。

↑ではDockerで試していた。Dockerはホストからカーネルパラメタを持ってきていて、起動時の`--sysctl`による上書きも（まだ）できない。

```shell-session
# docker run --rm -it --sysctl fs.protected_symlinks=0 centos:8
invalid argument "fs.protected_symlinks=0" for "--sysctl" flag: sysctl 'fs.protected_symlinks=0' is not whitelisted
See 'docker run --help'.
```

ホスト側で設定を変えるしかなさそう。

しかし……シンボリックを悪用した攻撃を考えてみても、/tmpに作っているシンボリックを他のディレクトリに作るだけなような。/tmpの中にstickyではないディレクトリを作るだけで良い。どういう攻撃が防げるのだろう :thinking: