ファイル機能を使用してsetuid-rootバイナリの必要性のほとんどを排除するGentooHardenedボックスを管理します(たとえば、_/bin/ping
_にはCAP_NET_RAWがあります)。
実際、私が残した唯一のバイナリはこれです:
_abraxas ~ # find / -xdev -type f -perm -u=s
/usr/lib64/misc/glibc/pt_chown
abraxas ~ #
_
Setuidビットを削除するか、ルートファイルシステムnosuid
を再マウントすると、sshdとGNU Screenは、マスター疑似端末でgrantpt(3)
を呼び出し、 glibcは明らかにこのプログラムを実行して、_/dev/pts/
_の下のスレーブ疑似端末をchownおよびchmodし、GNU Screenは、この関数がいつ失敗するかを気にします。
問題は、grantpt(3)
のマンページに、Linuxではdevpts
ファイルシステムがマウントされているため、そのようなヘルパーバイナリは必要ないと明示的に記載されていることです。カーネルは、スレーブのUIDとGIDを、(getpt(3)
を呼び出して)_/dev/ptmx
_を開いたプロセスの実際のUIDとGIDに自動的に設定します。
私はこれを実証するために小さなサンプルプログラムを書きました:
_#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int master;
char slave[16];
struct stat slavestat;
if ((master = getpt()) < 0) {
fprintf(stderr, "getpt: %m\n");
return 1;
}
printf("Opened a UNIX98 master terminal, fd = %d\n", master);
/* I am not going to call grantpt() because I am trying to
* demonstrate that it is not necessary with devpts mounted,
* the owners and mode will be set automatically by the kernel.
*/
if (unlockpt(master) < 0) {
fprintf(stderr, "unlockpt: %m\n");
return 2;
}
memset(slave, 0, sizeof(slave));
if (ptsname_r(master, slave, sizeof(slave)) < 0) {
fprintf(stderr, "ptsname: %m\n");
return 2;
}
printf("Device name of slave pseudoterminal: %s\n", slave);
if (stat(slave, &slavestat) < 0) {
fprintf(stderr, "stat: %m\n");
return 3;
}
printf("Information for device %s:\n", slave);
printf(" Owner UID: %d\n", slavestat.st_uid);
printf(" Owner GID: %d\n", slavestat.st_gid);
printf(" Octal mode: %04o\n", slavestat.st_mode & 00007777);
return 0;
}
_
前述のプログラムのsetuidビットを削除して動作を観察します。
_aaron@abraxas ~ $ id
uid=1000(aaron) gid=100(users) groups=100(users)
aaron@abraxas ~ $ ./ptytest
Opened a UNIX98 master terminal, fd = 3
Device name of slave pseudoterminal: /dev/pts/17
Information for device /dev/pts/17:
Owner UID: 1000
Owner GID: 100
Octal mode: 0620
_
この問題を回避する方法については、いくつかのアイデアしかありません。
1)プログラムを単に0を返すスケルトンに置き換えます。
2)何もしないようにlibcのgrantpt()にパッチを適用します。
これらの両方を自動化することはできますが、誰かが一方を他方に推奨したり、これを解決するための他の方法を推奨したりしますか?
これが解決したら、最終的に_mount -o remount,nosuid /
_できます。
glibcが適度に最新であり、devptsが正しく設定されている場合は、pt_chown
ヘルパーを呼び出す必要はまったくありません。 。
既知/潜在的な問題pt_chown
からsetuid-rootを削除している可能性があります。
grantpt()
サポートされているdevfs
fromglibc-2.7、glibc-2.11に変更が加えられましたただし、DEVFS_SUPER_MAGIC
を明示的にチェックするのではなく、chown()
を試行する前、またはpt_chown
の呼び出しにフォールバックする前に、何らかの作業を行う必要があるかどうかをチェックします。
glibc-2.17/sysdeps/unix/grantpt.c
から
...
uid_t uid = __getuid ();
if (st.st_uid != uid)
{
if (__chown (buf, uid, st.st_gid) < 0)
goto helper;
}
...
同様のスタンザを使用して、gidと権限を確認します。キャッチは、uid、gid、およびmodeが期待値と一致する必要があることです(you、tty、およびexactly620; /usr/libexec/pt_chown --help
で確認してください)。そうでない場合は、chown()
(呼び出し元のバイナリ/プロセスのCAP_CHOWN、CAP_FOWNERの機能が必要)が試行され、失敗した場合は、pt_chown
外部ヘルパー(setuid-rootである必要があります)が試行されます。 pt_chown
が機能を使用できるようにするには、それ(したがって、glibc)がHAVE_LIBCAP
でコンパイルされている必要があります。 しかし、pt_chown
は(glibc-2.17の時点で、バージョンを述べていませんが、あなたが指摘したように) )HAVE_LIBCAP
のgeteuid()==0
に関係なく、glibc-2.17/login/programs/pt_chown.c
からの関連コードが必要になるようにハードコードされています:
...
if (argc == 1 && euid == 0)
{
#ifdef HAVE_LIBCAP
/* Drop privileges. */
if (uid != euid)
...
#endif
/* Normal invocation of this program is with no arguments and
with privileges. */
return do_pt_chown ();
}
...
/* Check if we are properly installed. */
if (euid != 0)
error (FAIL_EXEC, 0, gettext ("needs to be installed setuid `root'"));
(機能を使用する前にgeteuid()==0
を期待することは、実際には機能の精神に基づいていないようです。これにバグを記録します。)
考えられる回避策は、影響を受けるプログラムにCAP_CHOWN、CAP_FOWNERを与えることかもしれませんが、もちろんそれをptyに制限することはできないので、私は本当にお勧めしません。
それでも問題が解決しない場合は、sshd
とscreen
にパッチを適用する方がglibcにパッチを適用するよりも少し不快です。ただし、問題はglibc内にあるため、よりクリーンなアプローチが選択的です DLLインジェクション を使用してダミーのgrantpt()
を実装します。