mod_ruid2はgroupsを元の状態に戻せないのでpatch作成

mod_ruid2はDSO版のPHP等を効率良くある程度安全に使うにはかなり良いモジュールである。

「ある程度」と書いたのは、それなりに脆弱性があるためで、それは今後紹介するとして今回はそこに関しては言及しない。

簡単なmod_ruid2の動作だが、preforkされているサーバプロセスに対してリクエストがあった場合、サーバプロセスそのものをリクエスト対象のファイルの権限(uidやgid等)もしくは任意 の権限に変更し、そのごリクエスト処理を行なって、最後に変更されたサーバプロセスの権限を元の権限に戻す。このあたりの細かい話はLinux Capabilityの話になってくるので省略する。

ところが、今回色々試していると、サーバプロセスのユーザー、例えばapacheユーザー等がgroupsの設定を持っていた場合には、groupsの権限は戻せない実装になっていた。そのため、ある ディレクトリをgroupsで設定された別のgroup、例えばwww-admin等で権限設定されていた場合、元のapacheユーザーはwww-adminにアクセスできるが、mod_ruidの処理後そのgroups設定がなくなってしまい、アクセスできなくなる。

例えば、/etc/groupsに以下のような設定がされていて、

www-admin:x:510:apache

とあるVirtualHost配下のディレクトリの権限が以下のようになっていた場合

drwxr-x--- 8 matsumotory www-admin 4096 10月  8 17:32 docroot/

mod_ruid2で権限が戻った後でもgroupsの設定が存在しないプロセス権限になってしまって、アクセスできなくなる。厳密には、preforkされているプロセスの数だけアクセスできた後、403で一切アクセスできなくなる。

mod_ruid2のgroupsの設定戻しの実装は以下のようになっていた。該当の関数がそれほど大きくなかったので、そのまま貼る。

377 /* run during request cleanup */
378 static apr_status_t ruid_suidback (void *data)
379 {
380     request_rec *r = data;
381
382     ruid_config_t *conf = ap_get_module_config (r->server->module_config, &ruid2_module);
383     core_server_config *core = (core_server_config *) ap_get_module_config(r->server->module_config, &core_module);
384
385     cap_t cap;
386     cap_value_t capval[3];
387
388     if (cap_mode == RUID_CAP_MODE_KEEP) {
389
390         cap=cap_get_proc();
391         capval[0]=CAP_SETUID;
392         capval[1]=CAP_SETGID;
393         capval[2]=CAP_SYS_CHROOT;
394         cap_set_flag(cap, CAP_EFFECTIVE, (conf->chroot_dir ? 3 : 2), capval, CAP_SET);
395         if (cap_set_proc(cap)!=0) {
396             ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s CRITICAL ERROR %s:cap_set_proc failed before setuid", MODULE_NAME, __func__);
397         }
398         cap_free(cap);
399
400         setgroups(0,NULL);
401         setgid(unixd_config.group_id);
402         setuid(unixd_config.user_id);
403
404         /* set httpd process dumpable after setuid */
405         if (coredump) {
406             prctl(PR_SET_DUMPABLE,1);
407         }
408
409         /* jail break */
410         if (conf->chroot_dir) {
411             if (fchdir(chroot_root) < 0) {
412                 ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s failed to fchdir to root dir (%d) (%s)", MODULE_NAME, chroot_root, strerror(errno));
413             } else {
414                 if (chroot(".") != 0) {
415                     ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s jail break failed", MODULE_NAME);
416                 }
417             }
418             core->ap_document_root = old_root;
419         }
420
421         cap=cap_get_proc();
422         capval[0]=CAP_SETUID;
423         capval[1]=CAP_SETGID;
424         capval[2]=CAP_SYS_CHROOT;
425         cap_set_flag(cap, CAP_EFFECTIVE, 3, capval, CAP_CLEAR);
426         if (cap_set_proc(cap)!=0) {
427             ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s CRITICAL ERROR %s:cap_set_proc failed after setuid", MODULE_NAME, __func__);
428         }
429         cap_free(cap);
430     }
431
432     return DECLINED;
433 }

上記実装の400から402行目において、元のサーバプロセスの権限にもどしているのだが、setgroupsは無しの設定に戻している。

400         setgroups(0,NULL);
401         setgid(unixd_config.group_id);
402         setuid(unixd_config.user_id);

ということで、これはよろしくないので、groupsを適宜取得し(child_initやinitでやるとうまくいかない)groupsも元通りにするpatchを書いた。以下ソース。

--- mod_ruid2.c.orig    2011-02-24 23:25:28.000000000 +0900
+++ mod_ruid2.c 2011-10-21 16:06:53.000000000 +0900
@@ -78,10 +78,12 @@

 typedef struct
 {
+    int server_groups_size;
        uid_t default_uid;
        gid_t default_gid;
        uid_t min_uid;
        gid_t min_gid;
+    gid_t *server_groups;
        int8_t stat_used;
        const char *chroot_dir;
        const char *document_root;
@@ -150,6 +152,8 @@
        conf->stat_used=UNSET;
        conf->chroot_dir=NULL;
        conf->document_root=NULL;
+    conf->server_groups=NULL;
+    conf->server_groups_size=0;

        return conf;
 }
@@ -397,7 +401,11 @@
                }
                cap_free(cap);

-               setgroups(0,NULL);
+        if (conf->server_groups_size == 0)
+            setgroups(0, NULL);
+        else
+            setgroups(conf->server_groups_size, conf->server_groups);
+
                setgid(unixd_config.group_id);
                setuid(unixd_config.user_id);

@@ -541,6 +549,16 @@
        cap_t cap;
        cap_value_t capval[2];

+    int     gidsize;
+    gid_t   *grouplist;
+
+    gidsize = getgroups(0, NULL);
+    grouplist = apr_pcalloc(r->pool, gidsize * sizeof(gid_t));
+    getgroups(gidsize, grouplist);
+
+    conf->server_groups = grouplist;
+    conf->server_groups_size = gidsize;
+
        if (dconf->ruid_mode==RUID_MODE_STAT) capval[ncap++] = CAP_DAC_READ_SEARCH;
        if (chroot_root != UNSET) capval[ncap++] = CAP_SYS_CHROOT;
        if (ncap) {

これで、よりmod_ruidな使い方ができるんではないだろうか。もしくは、ここで詰まっていた人は是非patch当ててみてほしい。ただし、mod_ruid2の使い方について気をつけないといけない 事があるので、それは次回に説明しようと思う。ヒントはREADMEに書かれた以下の文章。

there are some security issues, for instance if attacker successfully exploits the httpd 
process, he can set effective capabilities and setuid to root. i recommend to use some 
security patch in kernel (grsec),or something..

「mod_ruid2はgroupsを元の状態に戻せないのでpatch作成」への1件のフィードバック

コメントは受け付けていません。