web-dev-qa-db-ja.com

このglibcの問題を回避するための最良の方法は何でしょうか?

ファイル機能を使用して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 /_できます。

26
Aaron Jones

glibcが適度に最新であり、devptsが正しく設定されている場合は、pt_chownヘルパーを呼び出す必要はまったくありません。 。

既知/潜在的な問題pt_chownからsetuid-rootを削除している可能性があります。

grantpt()サポートされているdevfs fromglibc-2.7glibc-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_LIBCAPgeteuid()==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に制限することはできないので、私は本当にお勧めしません

それでも問題が解決しない場合は、sshdscreenにパッチを適用する方がglibcにパッチを適用するよりも少し不快です。ただし、問題はglibc内にあるため、よりクリーンなアプローチが選択的です DLLインジェクション を使用してダミーのgrantpt()を実装します。

2
mr.spuratic