所有者とグループを考慮したハードリンク作成制限機能の実装をソースコードから調べてみる

[NetBSD Advent Calendar 2024](https://qiita.com/advent-calendar/2024/netbsd) 11日目の記事です。
今日は[8日目の記事](https://qiita.com/furandon_pig/items/c988aadda7a0193165d3)で紹介した、所有者とグループを考慮したハードリンク作成制限機能について、ソースコードレベルでの処理がどうなっているかを見てみようと思います。

## ハードリンク作成時のuid,gidチェック

ハードリンク作成制限は、[sysctl(8)](https://man.netbsd.org/NetBSD-10.0/sysctl.8)の `security.models.extensions.hardlink_check_*` というMIB(Management Information Base)エントリの値を設定することで実現できます。

`hardlink_check_` というキーワードで `grep` してみます。どうやら `/usr/src/sys/secmodel/extensions/secmodel_extensions.c` というファイルでMIB用の変数定義と値の参照が行われているようです。

```sh
$ find /usr/src/sys -type f | xargs grep hardlink_check
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:static int hardlink_check_uid;
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:static int hardlink_check_gid;
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:                CTLTYPE_INT, "hardlink_check_uid",
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:                &hardlink_check_uid, 0,
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:                CTLTYPE_INT, "hardlink_check_gid",
/usr/src/sys/secmodel/extensions/secmodel_extensions.c:                &hardlink_check_gid, 0,
/usr/src/sys/secmodel/extensions/secmodel_extensions.c: if (hardlink_check_uid && kauth_cred_geteuid(cred) != va.va_uid)
/usr/src/sys/secmodel/extensions/secmodel_extensions.c: if (hardlink_check_gid && kauth_cred_groupmember(cred, va.va_gid) != 0)
```

[ファイル所有者以外のハードリンク作成を制限する](https://qiita.com/furandon_pig/items/c988aadda7a0193165d3#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%89%80%E6%9C%89%E8%80%85%E4%BB%A5%E5%A4%96%E3%81%AE%E3%83%8F%E3%83%BC%E3%83%89%E3%83%AA%E3%83%B3%E3%82%AF%E4%BD%9C%E6%88%90%E3%82%92%E5%88%B6%E9%99%90%E3%81%99%E3%82%8B)機能について見てみます。

52行目で `hardlink_check_uid` というMIBエントリの値を保持する変数が宣言され( `static` 宣言されているのでデフォルト値は `0`)、157～164行目で `sysctl` にMIBエントリが追加されています。

```c
 52 static int hardlink_check_uid;
 ...
157     sysctl_createv(clog, 0, &rnode, NULL,
158                CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
159                CTLTYPE_INT, "hardlink_check_uid",
160                SYSCTL_DESCR("Whether unprivileged users can hardlink "\
161                 "to files they don't own"),
162                sysctl_extensions_user_handler, 0,
163                &hardlink_check_uid, 0,
164                CTL_CREATE, CTL_EOL);
```

`hardlink_check_uid` の値がどのように参照されているか見てみます。548行目で `hardlink_check_uid` が真、かつ取得したuidが自分( `kauth_cred_t` で渡したクレデンシャル経由で取得しているっぽい)と異なる場合に `checkroot:` ラベルにジャンプし、 `root` ユーザでない場合はuidが異なる場合のハードリンク作成制限に該当するため、 `KAUTH_RESULT_DENY` を返すという挙動になっています。

gidをチェックする場合もほぼ同じ処理になっており、 `root` ユーザかどうかのチェックは共通化できるため、 `checkroot:` ラベルにジャンプするという実装になっているようです。

```c
533 static int
534 secmodel_extensions_vnode_cb(kauth_cred_t cred, kauth_action_t action,
535     void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
536 {
537     int error;
538     bool isroot;
539     struct vattr va;
...
544     error = VOP_GETATTR((vnode_t *)arg0, &va, cred);
...
548     if (hardlink_check_uid && kauth_cred_geteuid(cred) != va.va_uid)
549         goto checkroot;
...
554     return KAUTH_RESULT_DEFER;
555 checkroot:
556     error = secmodel_eval("org.netbsd.secmodel.suser", "is-root",
557         cred, &isroot);
558     if (error || !isroot)
559         return KAUTH_RESULT_DENY;
560
561     return KAUTH_RESULT_DEFER;
562 }
```

## まとめ

所有者とグループを考慮したハードリンク作成制限機能について、ソースコードレベルでの処理を見てみました。基本的には `sysctl` で機能が有効・無効であるかを判定し、有効な場合は然るべき判定処理(uidが違う場合は `root` ユーザかどうかをチェック)するという、シンプルな実装になっていました。ただ、これらの処理は[secmodel(9)](https://man.netbsd.org/NetBSD-10.0/secmodel.9)というフレームワークを使用しており、secmodel(9)の利用により実装しやすくなっているという側面もあるようです。