<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [kernel] 帶著問題看源碼 —— setreuid 何時更新 saved-set-uid (SUID)

      前言

      在寫《[apue] 進程控制那些事兒》/"進程創建"/"更改進程用戶 ID 和組 ID"一節時,發現 setreuid 更新實際用戶 ID (RUID) 或有效用戶 ID (EUID) 時,保存的設置用戶 ID (saved set-user-id SUID) 只會隨 EUID 變更,并不像 man 上說的會隨 RUID 變更 (man setreuid):

             If the real user ID is set (i.e., ruid is not -1) or the effective user ID is set to a value not equal to  the
             previous real user ID, the saved set-user-ID will be set to the new effective user ID.

      下面是實測結果:

      調用參數 (root 身份) RUID EUID SUID
      setreuid (bar, foo) bar foo foo
      setreuid (foo, bar) foo bar bar
      setreuid (-1, foo) root foo foo
      setreuid (bar, -1) bar root root
      setreuid (bar, bar) bar bar bar
      setreuid (foo, foo) foo foo foo

      特別是第 5 行 setreuid(bar, -1),RUID 變更為了 bar,SUID 仍保持 root 不變。

      為了解答這個問題,找來系統對應版本的 linux 源碼查看:

      > uname -a
      Linux goodcitizen.bcc-gzhxy.baidu.com 3.10.0-1160.80.1.el7.x86_64 #1 SMP Tue Nov 8 15:48:59 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

      這里是 3.10。之前推薦過 CodeBrowser,新近又發現一個源碼閱讀神器 bootlin

      能選代碼庫版本,功能上也比 CodeBrowser 好用,比如關鍵字搜索,不僅準確,還能把定義、聲明、成員、引用區分清楚,很好的解決了 CodeBrowser 找不到代碼的痛點,墻裂推薦~

      問題復現

      在擼源碼之前,先復習一下這個問題的來龍去脈,為節省讀者時間,把之前的 demo 貼上來 (setuid):

      #include "../apue.h"
      #include <sys/types.h>
      #include <sys/file.h>
      #include <sys/stat.h>
      #include <unistd.h>
      
      void print_ids ()
      {
          uid_t ruid = 0;
          uid_t euid = 0;
          uid_t suid = 0;
          int ret = getresuid (&ruid, &euid, &suid);
          if (ret == 0)
              printf ("%d: ruid %d, euid %d, suid %d\n", getpid(), ruid, euid, suid);
          else
              err_sys ("getresuid");
      }
      
      int main (int argc, char *argv[])
      {
          if (argc == 2)
          {
              char* uid=argv[1];
              int ret = setuid(atol(uid));
              if (ret != 0)
                  err_sys ("setuid");
      
              print_ids();
          }
          else if (argc == 3)
          {
              char* ruid=argv[1];
              char* euid=argv[2];
              int ret = setreuid(atol(ruid), atol(euid));
              if (ret != 0)
                  err_sys ("setreuid");
      
              print_ids();
          }
          else if (argc > 1)
          {
              char* uid=argv[1];
              int ret = seteuid(atol(uid));
              if (ret != 0)
                  err_sys ("seteuid");
      
              print_ids();
          }
          else
          {
              print_ids();
          }
      
          return 0;
      }

      做個簡單說明:

      • print_ids 打印當前進程 3 個權限ID:RUID / EUID / SUID,其中用到的 getresuid 僅 Linux 支持,能展示用戶當前的 SUID 值
      • ./setuid 123:觸發 setuid 調用,并打印調用后的結果
      • ./setuid 123 456:觸發 setreuid 調用,并打印調用后的結果
      • ./setuid 123 456 789:觸發 seteuid 調用,并打印調用后的結果。注意僅第一個參數有用,后兩個占位

      通過傳遞 setuid 程序不同參數,就可以驗證不同的接口了,這里只需驗證 setreuid,固定傳遞兩個參數即可。把測試腳本也貼上來 (setreuid.sh):

      #!/bin/sh
      groupadd test
      echo "create group ok"
      
      useradd -g test foo
      useradd -g test bar
      foo_uid=$(id -u foo)
      bar_uid=$(id -u bar)
      echo "create user ok"
      echo "    foo: ${foo_uid}"
      echo "    bar: ${bar_uid}"
      
      cd /tmp
      #chown bar:test ./setuid
      echo "test foo"
      ./setuid
      
      #chmod u+s ./setuid
      #echo "test set-uid bar"
      #su foo -c ./setuid
      
      echo "test setreuid(bar, foo)"
      ./setuid ${bar_uid} ${foo_uid}
      
      echo "test setreuid(foo, bar)"
      ./setuid ${foo_uid} ${bar_uid}
      
      echo "test setreuid(-1, foo)"
      ./setuid -1 ${foo_uid}
      
      echo "test setreuid(bar, -1)"
      ./setuid ${bar_uid} -1
      
      echo "test setreuid(bar, bar)"
      ./setuid ${bar_uid} ${bar_uid}
      
      echo "test setreuid(foo, foo)"
      ./setuid ${foo_uid} ${foo_uid}
      
      userdel bar
      userdel foo
      echo "remove user ok"
      
      rm -rf /home/bar
      rm -rf /home/foo
      echo "remove user home ok"
      
      groupdel test
      echo "delete group ok"

      自動創建測試賬戶并調用 setuid,驗證了 6 種用例,需要使用超級用戶身份啟動:

      > sudo sh setreuid.sh
      create group ok
      create user ok
          foo: 1003
          bar: 1004
      test foo
      27253: ruid 0, euid 0, suid 0
      test setreuid(bar, foo)
      27254: ruid 1004, euid 1003, suid 1003
      test setreuid(foo, bar)
      27255: ruid 1003, euid 1004, suid 1004
      test setreuid(-1, foo)
      27256: ruid 0, euid 1003, suid 1003
      test setreuid(bar, -1)
      27257: ruid 1004, euid 0, suid 0
      test setreuid(bar, bar)
      27258: ruid 1004, euid 1004, suid 1004
      test setreuid(foo, foo)
      27259: ruid 1003, euid 1003, suid 1003
      remove user ok
      remove user home ok

      現象與表中列出的一致。

      源碼分析

      在 kernel 3.10.0 版本中搜索 setreuid,沒搜到,可能是 kernel 在系統函數上加了一堆宏識別不了,搜索 setuid 可以,它倆在同一個文件:

      /*
       * Unprivileged users may change the real uid to the effective uid
       * or vice versa.  (BSD-style)
       *
       * If you set the real uid at all, or set the effective uid to a value not
       * equal to the real uid, then the saved uid is set to the new effective uid.
       *
       * This makes it possible for a setuid program to completely drop its
       * privileges, which is often a useful assertion to make when you are doing
       * a security audit over a program.
       *
       * The general idea is that a program which uses just setreuid() will be
       * 100% compatible with BSD.  A program which uses just setuid() will be
       * 100% compatible with POSIX with saved IDs. 
       */
      SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid)
      {
      	struct user_namespace *ns = current_user_ns();
      	const struct cred *old;
      	struct cred *new;
      	int retval;
      	kuid_t kruid, keuid;
      
      	kruid = make_kuid(ns, ruid);
      	keuid = make_kuid(ns, euid);
      
      	if ((ruid != (uid_t) -1) && !uid_valid(kruid))
      		return -EINVAL;
      	if ((euid != (uid_t) -1) && !uid_valid(keuid))
      		return -EINVAL;
      
      	new = prepare_creds();
      	if (!new)
      		return -ENOMEM;
      	old = current_cred();
      
      	retval = -EPERM;
      	if (ruid != (uid_t) -1) {
      		new->uid = kruid;
      		if (!uid_eq(old->uid, kruid) &&
      		    !uid_eq(old->euid, kruid) &&
      		    !nsown_capable(CAP_SETUID))
      			goto error;
      	}
      
      	if (euid != (uid_t) -1) {
      		new->euid = keuid;
      		if (!uid_eq(old->uid, keuid) &&
      		    !uid_eq(old->euid, keuid) &&
      		    !uid_eq(old->suid, keuid) &&
      		    !nsown_capable(CAP_SETUID))
      			goto error;
      	}
      
      	if (!uid_eq(new->uid, old->uid)) {
      		retval = set_user(new);
      		if (retval < 0)
      			goto error;
      	}
      	if (ruid != (uid_t) -1 ||
      	    (euid != (uid_t) -1 && !uid_eq(keuid, old->uid)))
      		new->suid = new->euid;
      	new->fsuid = new->euid;
      
      	retval = security_task_fix_setuid(new, old, LSM_SETID_RE);
      	if (retval < 0)
      		goto error;
      
      	return commit_creds(new);
      
      error:
      	abort_creds(new);
      	return retval;
      }

      代碼不長沒做刪減,主體就是下面的框架:

          ...
      	new = prepare_creds();
          ...
          old = current_cred();
          ...
      	return commit_creds(new);
      
      error:
      	abort_creds(new);

      prepare_creds 返回的 new 代表新用戶權限,會從當前權限復制一份;current_cred 返回的 old 代表原用戶權限。經過對 new 的一番操作,如果成功就將它提交 (commit_creds),原權限被替換;否則回滾 (abort_creds),原權限不變。現在關注焦點轉移到 new 的變更邏輯: 

      	retval = -EPERM;
      	if (ruid != (uid_t) -1) {
      		new->uid = kruid;
      		if (!uid_eq(old->uid, kruid) &&
      		    !uid_eq(old->euid, kruid) &&
      		    !nsown_capable(CAP_SETUID))
      			goto error;
      	}

       先看 ruid 參數,如果參數有效就將它設置到 new 的 uid,但需要同時滿足以下條件:

      • ruid == old->uid
      • ruid == old->euid
      • 原用戶具有超級用戶權限

      否則出錯。做為對比,再來看 euid 參數:

      	if (euid != (uid_t) -1) {
      		new->euid = keuid;
      		if (!uid_eq(old->uid, keuid) &&
      		    !uid_eq(old->euid, keuid) &&
      		    !uid_eq(old->suid, keuid) &&
      		    !nsown_capable(CAP_SETUID))
      			goto error;
      	}

      與 ruid 差不多:

      • euid == old->uid
      • euid == old->euid
      • euid == old->suid
      • 原用戶具有超級用戶權限

      多了一條規則,可以將 euid 設置為 old->suid。最后來看 SUID 的更新規則:

      	if (ruid != (uid_t) -1 ||
      	    (euid != (uid_t) -1 && !uid_eq(keuid, old->uid)))
      		new->suid = new->euid;
      	new->fsuid = new->euid;

      第三行準確無誤的告訴我們,new ->suid 是固定被設置為 new->euid 的,時機是以下條件之一:

      • ruid 有效
      • euid 有效且 euid 與原 RUID 不同

      看起來 ruid 參數只是影響 SUID 要不要從新 EUID 復制,即便 RUID 沒變更、只要 ruid 參數有效就能產生這種作用。再回顧一下 man 的說明:

             If the real user ID is set (i.e., ruid is not -1) or the effective user ID is set to a value not equal to  the
             previous real user ID, the saved set-user-ID will be set to the new effective user ID.

      簡直就是代碼的"直譯",包括對 ruid 參數有效的說明、對 euid 參數與原 RUID 不同的說明、對 SUID 從新 EUID 復制的說明,一毛一樣。之前把這里理解成 SUID 從 RUID 復制了,粗心大意了!

      問題驗證

      了解 SUID 設置規則后,回頭來看上面的表,有進一步的理解:

      調用參數 (root 身份) RUID EUID SUID SUID 復制
      setreuid (bar, foo) bar * foo * foo 條件 I
      setreuid (foo, bar) foo * bar * bar 條件 I
      setreuid (-1, foo) root foo * foo 條件 II
      setreuid (bar, -1) bar * root root 條件 I
      setreuid (bar, bar) bar * bar * bar 條件 I
      setreuid (foo, foo) foo * foo * foo 條件 I

      表中第二列中的星號表示 ruid 參數有效;第三列的星號表示 euid 變更 (!= old.uid);由于 2 個條件之間是短路或的關系,第一個條件滿足后就不再檢測第二個條件,所以需要最后一列表示 SUID 復制最終是哪個條件觸發的。看起來大部分是條件一 ruid 有效,這些用例對條件二的測試不足,需要構造一組新的用例進行驗證。

      考查這樣一個場景,將 demo 設置為 set-user-id 為 root,以普通用戶 foo 啟動該進程后:RUID = foo、EUID = SUID = root,此時 RUID 與 EUID 不同,滿足了上述的條件二;保持 RUID 無效 (-1) 不滿足條件一,是不是就能走條件二了?下面來做個測試 (setreuid-setroot.sh):

      #!/bin/sh
      groupadd test
      echo "create group ok"
      
      useradd -g test foo
      useradd -g test bar
      foo_uid=$(id -u foo)
      bar_uid=$(id -u bar)
      root_uid=0
      echo "create user ok"
      echo "    foo: ${foo_uid}"
      echo "    bar: ${bar_uid}"
      echo "    root: ${root_uid}"
      
      cd /tmp
      chown root:test ./setuid
      echo "test foo"
      su foo -c ./setuid
      
      chmod u+s ./setuid
      echo "test set-uid root"
      su foo -c ./setuid
      
      echo "test setreuid(-1, foo)"
      su foo -c "./setuid -1 ${foo_uid}"
      
      echo "test setreuid(-1, bar)"
      su foo -c "./setuid -1 ${bar_uid}"
      
      echo "test setreuid(foo, foo)"
      su foo -c "./setuid ${foo_uid} ${foo_uid}"
      
      echo "test setreuid(root, foo)"
      su foo -c "./setuid ${root_uid} ${foo_uid}"
      
      userdel foo
      userdel bar
      echo "remove user ok"
      
      rm -rf /home/foo
      rm -rf /home/bar
      echo "remove user home ok"
      
      groupdel test
      echo "delete group ok"

      與之前的最大區別是,這里使用 foo 用戶身份啟動測試程序 (su foo -c),且它是 set-user-id 為 root 的。驗證以下 4 種 setreuid 用例:

      > sudo sh setreuid-setroot.sh
      create group ok
      create user ok
          foo: 1003
          bar: 1004
          root: 0
      test foo
      29332: ruid 1003, euid 1003, suid 1003
      test set-uid root
      29345: ruid 1003, euid 0, suid 0
      test setreuid(-1, foo)
      29357: ruid 1003, euid 1003, suid 0
      test setreuid(-1, bar)
      29369: ruid 1003, euid 1004, suid 1004
      test setreuid(foo, foo)
      29382: ruid 1003, euid 1003, suid 1003
      test setreuid(root, foo)
      29396: ruid 0, euid 1003, suid 1003
      remove user ok
      remove user home ok
      delete group ok

      清晰起見列表如下:

      調用參數 (foo 身份 set-user-id  root) RUID EUID SUID SUID 復制
      啟動后 foo root root n/a
      setreuid (-1, foo) foo foo root 未觸發
      setreuid (-1, bar) foo bar * bar 條件 II
      setreuid (foo, foo) foo * foo foo 條件 I
      setreuid (root, foo) root * foo foo 條件 I

      表中星號含義同前,下面分別解釋:

      • 第 3 行:僅設置 euid 為 foo,此時 ruid == -1 條件 1 不觸發;euid == old.uid 條件 2 不觸發,所以 SUID 保持 root 不變
      • 第 4 行:僅設置 euid 為 bar,此時 ruid == -1 條件 1 不觸發;euid != old.uid 條件 2 觸發,所以 SUID 被復制為新 EUID:bar
      • 第 5 行:同時設置 ruid 和 euid 為 foo,此時 ruid != -1 條件 1 觸發;euid == old.uid 條件 2 不觸發,所以 SUID 被復制為新 EUID:foo
      • 第 6 行:設置 ruid 為 root,euid 為 foo,此時 ruid != -1 條件 1 觸發;euid == old.uid 條件 2 不觸發,最終 SUID 被復制為新 EUID:foo

      與源碼邏輯對應上了,正好也解釋了原文《[apue] 進程控制那些事兒》中在這種場景下 setreuid(-1, foo) 用例中 SUID 保持為 root 的疑惑。

      看看上表中第 3 行在 setreuid(-1,foo) 后的情形:RUID = EUID = foo,SUID = root,如果此時調用 setreuid(foo, -1) 按理說 SUID 會被更新為 foo,一試究竟:

      uid_t ruid = 0;
          uid_t euid = 0;
          uid_t suid = 0;
          int ret = getresuid (&ruid, &euid, &suid);
          if (ret == 0)
          {
              printf ("%d: ruid %d, euid %d, suid %d\n", getpid(), ruid, euid, suid);
      #ifdef TEST_UPDATE_RUID
              if (ruid == euid && euid != suid)
              {
                  printf ("all uid same except suid %d, try to update ruid\n", ruid);
                  ret = setreuid (ruid, -1);
                  if (ret != 0)
                      err_sys ("setreuid");
                  else
                  {
                      getresuid (&ruid, &euid, &suid);
                      printf ("%d: ruid %d, euid %d, suid %d\n", getpid(), ruid, euid, suid);
                  }
              }
      #endif
      
          }
          else
              err_sys ("getresuid");

      在 print_ids 中檢測到 RUID = EUID != SUID 時,將 RUID 重新設置一下,ruid 參數與原 RUID 一致。重新運行:

      > sudo sh setreuid-setroot.sh
      ...
      test set-uid root
      29511: ruid 1003, euid 0, suid 0
      test setreuid(-1, foo)
      29523: ruid 1003, euid 1003, suid 0
      all uid same except suid 1003, try to update ruid
      29523: ruid 1003, euid 1003, suid 1003
      ...

      SUID 果然隨之變更了!這個用例更能說明問題,因為調用 setreuid 前后 RUID 與 EUID 沒有發生改變,SUID 卻因為 ruid 參數有效而發生了變更,有點意思。下面的表總結了上述過程:

      調用參數 (foo 身份 set-user-id  root) RUID EUID SUID SUID 復制
      啟動后 foo root root n/a
      setreuid (-1, foo) foo foo root 未觸發
      setreuid (foo, -1) foo * foo foo 條件 I

      這個表與之前不同的是所有 setreuid 調用均在一個進程中。

      意義探尋

      知其然,還要知其所以然,上面的探索只是第一步,對于 SUID 復制 EUID 的目的,《[apue] 進程控制那些事兒》已有討論,這里聚焦 SUID 何時復制 EUID,按照直覺設計成下面的條件看起來更通順:

      ruid != old.uid || euid != old.euid

      為了解答這個疑問,就按照設想的條件重跑一下 setreuid 的所有用例:

      調用參數 (root 身份) RUID EUID SUID
      setreuid (bar, foo) bar foo foo
      setreuid (foo, bar) foo bar bar
      setreuid (-1, foo) root foo foo
      setreuid (bar, -1) bar root root
      setreuid (bar, bar) bar bar bar
      setreuid (foo, foo) foo foo foo
      調用參數 (foo 身份 set-user-id  root) RUID EUID SUID
      啟動后 foo root root
      setreuid (-1, foo) foo foo foo
      setreuid (-1, bar) foo bar bar
      setreuid (foo, foo) foo foo foo
      setreuid (root, foo) root foo foo

      發現只有一個用例的結果會發生變化 (表中高亮字體):進程 set-user-id 為 root 且以普通用戶身份啟動 setreuid(-1, foo),從 SUID 不變到現在 SUID 跟隨 EUID 改變,這導致整個進程變為普通進程失去重新轉變為特權進程的機會。再看這個調用形式特別眼熟,這不就是 seteuid 嘛!它在改變 EUID 時是不希望 SUID 變更的,所以這下全明白了:setreuid 這樣的設計是為了給 seteuid 切換特權身份留后門,從而有機會再切換回之前的身份

      結語

      關于 seteuid,man 中有一段說明:

             Under  libc4,  libc5  and glibc 2.0 seteuid(euid) is equivalent to setreuid(-1, euid) and hence may change the
             saved set-user-ID.  Under glibc 2.1 and later it is equivalent to setresuid(-1, euid, -1) and hence  does  not
             change  the  saved  set-user-ID.  Analogous remarks hold for setegid(), with the difference that the change in
             implementation from setregid(-1, egid) to setresgid(-1, egid, -1) occurred in glibc 2.2 or 2.3 (dependeing  on
             the hardware architecture).

      大意是說 seteuid 到底等價于setreuid(-1,euid) 還是setresuid(-1,euid,-1)要看 glibc 版本,前者在改變 SUID 的邏輯上遵循上面的討論;后者不遵循,或者說 SUID 不會隨 EUID 變更。之前曾經比對過 setuid / setreuid / seteuid,并且推薦使用 seteuid,如果 seteuid 只是 setreuid 的分身,則它們的區別沒想象那么大,只是寫起來更方便一些。

      后記

      文章最后再推薦一波 bootlin:

      代碼庫除了 kernel,還可以選擇 freebsd、glibc、qemu、dpdk、grub、llvm、busybox 等,點擊符號跳轉,使用瀏覽器后退做調用棧回退,非常方便。

      參考

      [1]. https://elixir.bootlin.com

      posted @ 2024-04-10 11:41  goodcitizen  閱讀(130)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产一区二区日韩在线| 熟女系列丰满熟妇AV| 久久精品国产久精国产| 国产又黄又爽又不遮挡视频| 日韩免费美熟女中文av| 欧美肥老太wbwbwbb| 亚洲国产精品综合久久2007| 人妻av无码系列一区二区三区| 婷婷久久香蕉五月综合加勒比| 国产精品乱码久久久久久小说| 亚洲av影院一区二区三区| 久久精品国产色蜜蜜麻豆| 呻吟国产av久久一区二区| 日韩中文字幕有码av| 无套内谢少妇毛片aaaa片免费| 久久精品国产精品亚洲| 国产盗摄xxxx视频xxxx| 性做久久久久久久久| 国产精品自在拍首页视频| 岛国最新亚洲伦理成人| 中文字幕一区二区人妻电影 | 色噜噜噜亚洲男人的天堂| 天峻县| 国产亚洲一区二区三区av| 亚洲一区中文字幕人妻| 亚洲加勒比久久88色综合| 狠狠色丁香婷婷综合久久来来去 | 不卡在线一区二区三区视频 | 亚洲免费视频一区二区三区| 污网站在线观看视频| 国产综合色一区二区三区| 久久99国产精一区二区三区! | 久久中精品中文字幕入口| 欧美一区二区三区成人久久片| 久久精品国产99亚洲精品| 麻豆一区二区三区精品视频| 午夜精品区| 国产在线一区二区不卡| 黄色三级亚洲男人的天堂| 农民人伦一区二区三区| 深夜精品免费在线观看|