2013-07-19 27 views
4

我的自定義程序以非root權限執行,其用戶標識爲:uid: 1000 euid: 0,其中fork()後,在子進程中調用execv()以運行SSH客戶端服務。由於我在非特權用戶下啓動程序,因此當試圖將套接字綁定到設備時,Linux內核會執行所有權限檢查,導致子例程sock_setbindtodevice()中的一個故障,同時檢查CAP_NET_RAW功能,如下所示。更改用戶ID以分配附加功能

我在想的解決方案是首先在子進程中獲取root權限,執行特權操作,例如設置所需的功能,然後退回到非root用戶。

這裏的一個問題是,下拉到非根需要什麼,因爲執行ssh命令時,我希望生成的DSA/RSA鍵存儲在$HOME/.ssh/known_hosts而不是root/.ssh/known_hosts中。

請找到下面的代碼片段:

void 
global_exec_func (const char *proc_name, const char *proc_path, char **arg_list) 
{ 
    pid_t pid; 
    int  status, euid; 

    struct __user_cap_header_struct cap_header_data; 
    cap_user_header_t cap_header = &cap_header_data; 
    struct __user_cap_data_struct cap_data_data; 
    cap_user_data_t cap_data = &cap_data_data; 

    pid = fork(); 
    if (pid < 0) { 
     printf("%% can't fork process %s", proc_name); 
     return; 
    } 

    /* 
    * Child process. 
    */ 
    if (pid == 0) { 
     euid = geteuid();   /* Storing euid */ 

     /*Gaining root privileges */ 
     if (setuid(0) < 0) {  
      printf("setuid(0) failed"); 
     } 
     printf("After setting: getuid: %d geteuid: %d\n", getuid(), 
        geteuid()); 

     cap_header->pid = 0; 
     cap_header->version = _LINUX_CAPABILITY_VERSION; 

     /* Get the capabilities */ 
     if(capget(cap_header, cap_data) < 0) { 
      printf("failed capget error:%s", strerror(errno)); 
     } 
     cap_data->effective = (1 << CAP_NET_RAW); /* Set bit 13 */ 
     cap_data->inheritable = 0; 

     /* Set the capabilities */ 
     if (capset(cap_header, cap_data) < 0) { 
      printf("failed capset error:%s", strerror(errno)); 
     } 

     /* Drop back privileges */ 
     if (seteuid(euid) < 0) {  
      printf("seteuid(euid) failed"); 
     } 
     printf("After drop: getuid: %d geteuid: %d\n", getuid(), geteuid()); 

     prctl(PR_SET_KEEPCAPS, 1); 
     execv(proc_path, arg_list); 
     exit(1); 
    } 
    /* 
     * Parent Process code follows 
     */ 


Result: 
[local]linux#ssh 101.1.1.101 
After setting: getuid: 0 geteuid: 0 
After drop: getuid: 0 geteuid: 0 
The authenticity of host '101.1.1.101 (101.1.1.101)' can't be established. 
DSA key fingerprint is 0c:61:df:01:93:74:1f:5f:49:34:f4:4e:06:e8:d7:5f. 
Are you sure you want to continue connecting (yes/no)? ^C 
[local]linux# 

結果表明,SSH是成功的,但該方案在這一點上,這是不正確運行爲root。我如何返回到UID爲uid: 1000 euid: 0,以便ssh密鑰存儲在正確的目錄中。

請在我的解決方案上提出意見和建議,它真的能解決問題嗎?

回答

5

如果您的程序使用有效的用戶ID root執行,那麼您確實擁有root權限。


在Linux中,capabilities分爲三組:可繼承,允許和有效。可繼承定義了exec()中允許保留哪些功能。允許定義哪些功能適用於某個流程。有效定義哪些功能當前有效。

編輯添加:當包含二進制文件系統將支持文件系統功能,這些總是影響執行的進程將具有什麼功能。在man 7 capabilities手冊頁中的execve()期間查看能力轉換。

將進程的所有者或組從根目錄更改爲非根目錄時,始終清除有效功能集。默認情況下,允許的能力集也被清除,但在身份更改之前調用prctl(PR_SET_KEEPCAPS, 1L)會告知內核保持允許的集合不變。

因此,要獲得CAP_NET_RAW的功能,您的程序必須在允許的和有效的集合中擁有它。如果您希望CAP_NET_RAWexec()上保持有效,則必須將其包含在所有三個功能集中。

編輯補充:如果文件功能都支持的exec()的目標,文件功能還必須包含在繼承和有效集中的能力。 (只有在繼承的和有效的集合中包含該能力纔會授予該能力,因爲它不在文件能力允許的範圍內;但是,如果執行者具有將執行者的能力傳遞給執行者的能力就足夠了能力)。


您可以使用setcap命令將特定功能授予二進制文件。 (大多數Linux文件系統現在支持這些文件功能)。它不需要是特權,或者setuid的。只要記住將所需的功能添加到允許的和有效的設置。

編輯補充一些例子:

格蘭特CAP_NET_RAW/usr/bin/myprog(必須不能爲setuid或setgid根):

sudo setcap 'cap_net_raw=pe' /usr/bin/myprog 

默認情況下,不授予CAP_NET_RAW/usr/bin/myprog,但如果執行有能力(在可繼承和允許的集合中),保留能力(在可繼承和允許的集合中,並在有效集合中激活它):

sudo setcap 'cap_net_raw=ie' /usr/bin/myprog 

如果您的程序必須是setuid root,那麼您可以使用例如

#define _GNU_SOURCE 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/capability.h> 
#include <sys/prctl.h> 

#define NEED_CAPS 1 
static const cap_value_t need_caps[NEED_CAPS] = { CAP_NET_RAW }; 

int main(void) 
{ 
    uid_t real = getuid(); 
    cap_t caps; 

    /* Elevate privileges */ 
    if (setresuid(0, 0, 0)) 
     return 1; /* Fatal error, probably not setuid root */ 

    /* Add need_caps to current capabilities. */ 
    caps = cap_get_proc(); 
    if (cap_set_flag(caps, CAP_PERMITTED, NEED_CAPS, need_caps, CAP_SET) || 
     cap_set_flag(caps, CAP_EFFECTIVE, NEED_CAPS, need_caps, CAP_SET) || 
     cap_set_flag(caps, CAP_INHERITABLE, NEED_CAPS, need_caps, CAP_SET)) 
     return 1; /* Fatal error */ 

    /* Update capabilities */ 
    if (cap_set_proc(caps)) 
     return 1; /* Fatal error */ 

    /* Retain capabilities over an identity change */ 
    if (prctl(PR_SET_KEEPCAPS, 1L)) 
     return 1; /* Fatal error */ 

    /* Return to original, real-user identity */ 
    if (setresuid(real, real, real)) 
     return 1; /* Fatal error */ 

    /* Because the identity changed, we need to 
    * re-install the effective set. */ 
    if (cap_set_proc(caps)) 
     return 1; /* Fatal error */ 

    /* Capability set is no longer needed. */ 
    cap_free(caps); 

    /* You now have the CAP_NET_RAW capability. 
    * It will be retained over fork() and exec(). 
    */ 

    return 0; 
} 
+0

感謝明確的解釋。但是,通過啓用'cap_net_raw'功能,相同的實現無法解決。甲調試打印'的printf( 「%S \ n」 個,cap_to_text(cap_get_proc(),NULL));'之前和之後'cap_set_proc()'顯示爲'組PROC之前= EP組PROC後= EP CAP_NET_RAW + i',這是否告訴我們能力已經確定? –

+0

'= ep cap_net_raw'表示'CAP_NET_RAW'能力是有效的並且被允許的,這意味着當前進程確實具有這種能力。 '= EP CAP_NET_RAW + i'意味着'CAP_NET_RAW'能力是有效的,允許的,和繼承,這意味着當前的進程確實有這樣的能力,並且所有子進程它產生就會有這樣的能力了。你確實意識到許多應用程序像'ssh'做了他們自己的內部健康檢查?例如,即使你給'在/ usr/bin中/ ssh'了'CAP_NET_BIND_SERVICE'能力,在默認情況下它仍然拒絕綁定端口<1024 –

+0

@SunEric:我覺得我並沒有強調效果*文件系統能力*確實超過了'exec()'。我更新了我的答案以反映這些(請參閱「編輯添加:」分數)。 –