SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

Linux umask 預設權限怎麼決定?

umask 會影響每個新檔案與目錄的權限。實際顯示遮罩、建立檔案並讀回模式,了解一般使用者與 root 的差異,以及目錄缺少執行權限時為何無法進入。

Linux 中 umask 的作用

每個 Linux 程序都帶有一個 umask 值。這個值決定該程序建立的每個檔案與目錄的模式。程式在建立時會向 kernel 要求一組權限。kernel 會清除遮罩指定的每個位元,並套用剩餘的權限。umask 不會授予存取權限,只會從建立程式要求的權限中移除位元。

這個值不是發行版的固定屬性,而取決於目前使用的帳號及 shell 的啟動方式。同一台機器在相同時間使用標準映像檔時,這兩個值仍可能不同。因此,第一步不是查閱手冊,而是在目前這台機器上實際測量。

在目前所在的 shell 中顯示 umask

umask
umask -S

第一種形式會以八進位顯示遮罩。第二種形式會以該遮罩允許的權限顯示,格式與 chmod 接受的符號表示法相同。保留這兩行在畫面上。以下內容都會與 shell 剛才顯示的結果比較。

umask 是 shell builtin,不是磁碟上的程式。使用 type umask 確認這點。這一點很重要,因為 builtin 會直接變更 shell 程序本身。獨立程式只能變更自己的程序,之後結束並帶走該變更。

建立檔案與目錄,然後讀回其模式

cd "$(mktemp -d)"
touch probe.file
mkdir probe.dir
stat -c '%a %A %n' probe.file probe.dir

%a 會以八進位格式輸出模式,而 %A 會以 ls -l 所使用的 drwxr-xr-x 格式輸出相同模式。將這兩行分別與剛才輸出的遮罩比較。遮罩中設定的每個位元都會從模式中缺少,因為遮罩唯一會做的事就是清除這些位元。若 %A 欄位目前仍不易讀懂,請先弄清楚 drwxr-xr-x 權限字串 的意義。

檔案與目錄彼此不同,而這項差異不是由遮罩造成的。touch 向 kernel 要求 owner、group 與 other 的讀取和寫入權限。mkdir 則向三者要求讀取、寫入與執行權限。相同的遮罩會從兩個不同的要求中扣除。因此,touch 建立的檔案永遠不會具備可執行權限,不論遮罩的內容為何:因為從未要求執行位元,而遮罩無法把位元加回去。

( umask a=rwx; touch open.file; stat -c '%a %A %n' open.file )

這些括號會在 subshell 中執行命令,因此變更會隨 subshell 結束而消失。現在遮罩不要求清除任何位元,而 stat 仍會回報檔案沒有執行位元。之後再次執行 umask,原始值就會恢復,這表示此設定存在於 process 內,並由子 process 繼承,而不是儲存在磁碟上。

執行位元遭清除時,目錄最能明顯看出影響。

( umask a=rw; mkdir noexec.dir; cd noexec.dir )

以一般使用者身分執行時,cd 會因 bash: cd: noexec.dir: Permission denied 而失敗,因為遮罩清除了 mkdir 所要求的執行位元,而沒有執行位元的目錄無法進入。root 會略過這項檢查,因此這個問題只會在一般帳號上顯現。

為什麼 root 與你自己的使用者看到不同的 umask

透過另一個帳號,並以不同方式啟動相同的測量,然後將兩個輸出並列比較。

umask
sudo -i umask

sudo -i 會啟動 root 的登入 shell,並在其中執行內建指令。因此,這是另一個帳號透過不同啟動路徑進入系統。在原始的 Ubuntu 與 Debian server 映像檔上,這兩行可能會輸出不同值。兩行都正確。每一行都顯示其自身啟動路徑產生的結果;本文接下來將說明這個結果是由系統的哪個部分產生。

哪個映像檔中的檔案決定了這個值

grep -nE '^(UMASK|USERGROUPS_ENAB)' /etc/login.defs
grep -rn pam_umask /etc/pam.d/
grep -rn umask /etc/profile /etc/profile.d/ /etc/bash.bashrc ~/.profile ~/.bashrc 2>/dev/null || echo 'no umask line in the shell startup files'

如果第一次執行 grep 沒有輸出,請移除 ^ anchor 後再執行一次:該行可能被註解掉,而註解行屬於說明文件,不是設定。第三個 grep 才是最容易讓人意外的部分。在 Debian 和 Ubuntu 中,隨附的 /etc/profile 通常會指向 PAM,而不是自行設定 mask,因此你原本以為負責此設定的檔案,往往並不是實際來源。grep 找不到相符內容時會以非零狀態結束,這就是該行最後加上 || echo 的原因:如果映像檔中的啟動檔案都沒有提及 mask,你會看到訊息而不是沒有任何輸出,而這則訊息就是要找的結果。

/etc/login.defs 宣告一個值,PAM 套用另一個值

`UMASK 中的 /etc/login.defs 行,是多數指南引用的值。核心與 shell 都不會讀取該檔案。該檔案由 pam_umask 讀取;這是 PAM(可插拔驗證模組)模組,會在建立工作階段時執行。pam_umask 會採用找到的第一個值,順序如下:使用者 GECOS 欄位中的 umask= 項目、直接寫在 pam_umask.so 行上的 umask= 引數,最後是 UMASK(來源為 /etc/login.defs)。各發行版可能會修改此模組,因此請在自己的映像檔上執行 man pam_umask`,並依照輸出的順序判讀。

因此,`/etc/login.defs 可以宣告一個值,但工作階段最後套用另一個值,而且兩邊都不會顯示警告。這些 grep 指令可協助你確認目前屬於哪種情況。如果 pam_umask.so 行帶有自己的 umask= 引數,login.defs` 中的設定就會失效。

USERGROUPS_ENAB 與 root 例外

id -un
id -gn

如果這兩個指令輸出的名稱相同,表示您使用的是使用者私有群組:建立帳號時,也建立了以該帳號命名的專屬群組。pam_umask 的 usergroups 行為由 USERGROUPS_ENAB(位於 /etc/login.defs)控制。啟用此設定時,只要帳號不是 root,且主要群組名稱與使用者名稱相同,該模組就會將遮罩的擁有者位元複製到群組位元。如此一來,工作階段結束時所使用的遮罩會讓該帳號建立的所有項目保留開放的群組位元。root 由模組本身排除在外,而這項排除是同一台伺服器上的兩個 shell 輸出不同遮罩時,最常見的單一原因。

這項規則的依據是,私有群組只有一名成員,因此群組可寫入等同於擁有者可寫入,不會造成其他影響。直到有人將第二名成員加入該群組為止。從那一刻起,這名新成員即可寫入該帳號過去建立的每個檔案,而不需要對這些檔案執行任何命令來啟用這項權限。請為每項服務提供 專屬的最小權限使用者帳號,讓該群組刻意維持為只有一名成員的群組。

登入 shell、非登入 shell 與非互動式 shell

umask
bash -lc 'umask'
bash -c 'umask'

PAM 會在建立工作階段時執行:login在主控台登入時、sshdsusudo -i。某個 shell 啟動另一個 shell 時,不會執行 PAM。bash -l是登入 shell,因此會讀取 /etc/profile~/.profile,但不會呼叫 pam_umask,因為沒有建立新的工作階段。bash -c不會讀取這兩個檔案,而是繼承啟動它的程序所使用的遮罩。cron 工作、git hook,以及由服務管理器啟動的程式都屬於最後一種情況,因此它們使用的遮罩取決於父程序。

因此,「我已在 /etc/profile 中設定,但服務寫入的模式仍然錯誤」是很常見的回報。該服務根本沒有讀取那個檔案。

設定位置:確保設定在重新啟動後仍然生效

請在工作負載實際啟動的位置設定 umask,因為每個啟動途徑讀取的檔案不同。

  1. 對於登入系統的帳戶:在 /etc/login.defs 中設定 UMASK,由 pam_umask 套用至機器上的每個工作階段。這是整台機器的設定,因此會同時影響所有帳戶。
  2. 對於單一帳戶:pam_umask.so 行中的 umask= 引數同樣是整台機器的設定,因此個別使用者的值應放在該使用者的 GECOS 欄位中;登入 shell 使用 ~/.profile,互動式 shell 使用 ~/.bashrc
  3. 對於由 systemd 管理的 daemon:在 unit 的 [Service] 區段中設定 UMask=。unit 由 service manager 啟動,因此不會讀取 /etc/profile,pam_umask 也不會執行。在這些設定中,只有 unit file 的設定會套用至 daemon。
  4. 對於由 cron 或 hook 啟動的 script:在第一行明確設定 umask,而且要先於 script 建立任何檔案。
[Service]
UMask=<the octal mask you chose>

接著,從相同啟動途徑重新啟動後進行驗證,不要使用編輯檔案時所在的 shell。當前 shell 已經保存自己的 umask;編輯設定檔不會回溯套用至正在執行的 process。

bash -lc 'umask'
sudo -i umask

為何事後執行 chmod 不是相同的修正方式

chmod會修正已存在的檔案。mask 會決定尚未建立之檔案的 mode。在目錄上執行 chmod -R 後,服務下一次寫入的檔案仍會帶有舊的 mode,因為該 mode 來自建立檔案的程序,而目錄內容不會改變它。

此外還存在一段時間差。在檔案建立到執行 chmod 之間,檔案會以較寬鬆的 mode 存在於磁碟上,任何能讀取該目錄的程序都能開啟它。對私密金鑰或備份封存檔而言,這段時間正是你想消除的風險。

請改在建立時設定 mode。install -m u=rw,go= newfile /etc/app/newfile會以明確的 mode 寫入目的地,目錄則使用 mkdir -m。兩者都會採用你指定的 mode,並忽略 mask。ssh-keygen會設定其寫入之私密金鑰的 mode,因此在其他檔案都不正確的主機上,該檔案通常仍會正確。

SSH 通常最容易受到影響。使用一般的 mkdir 建立的 ~/.ssh,或使用 cat >> 附加的 authorized_keys,會採用 shell 的 mask。啟用 StrictModes 後,如果金鑰檔案位於群組可寫入的目錄中,sshd 會拒絕讀取。用戶端會顯示 Permission denied (publickey),而伺服器的 /var/log/auth.log 會記錄真正原因:

Authentication refused: bad ownership or modes for directory /home/deploy/.ssh

這項檢查是刻意設計的,而 在 VPS 上強化 SSH 取決於此檢查正常運作。請在新伺服器上建立將使用這些帳號的帳戶之前,先測量 mask,並一併參考 新 VPS 啟用後的前 10 分鐘;如此一來,這些帳戶寫入之每個檔案的 mode 都會預先確定。

複製與封存作業會忽略 mask

cp -prsync -a 會還原來源檔案所記錄的模式,因此 mask 不會影響結果。tar 以 root 身分解壓縮時也會如此;一般使用者搭配 -p 解壓縮時亦然。從備份還原的檔案會保留建立備份時的模式。下結論說正確的 mask 被忽略前,請先確認這一點:對還原的資料而言,系統根本不會查閱 mask。

FAQ

為什麼我的 cron 工作建立的檔案,其模式與我的 ssh 工作階段不同?

cron 工作不是登入工作階段,因此不會執行 pam_umask,也不會讀取 /etc/profile~/.profile。它會繼承啟動該工作的程序所使用的遮罩。在腳本第一行加入明確的 umask,並放在建立任何檔案之前;同時從工作內部輸出一次遮罩,這樣才能確認該工作實際使用的設定,而不是目前 shell 的設定。

為什麼 /etc/login.defs 的設定與我的 shell 輸出結果不同?

UMASK 位於 /etc/login.defs 中時,只是 pam_umask 最後採用的備援值。此模組會優先使用使用者 GECOS 欄位中的 umask= 項目,接著使用 /etc/pam.d/pam_umask.so 行上的 umask= 引數。由 USERGROUPS_ENAB 開啟的 usergroups 行為,接著會改寫任何非 root 帳號的群組位元,前提是該帳號的主要群組名稱與帳號名稱相同。執行 grep -rn pam_umask /etc/pam.d/id -un; id -gn,即可確認哪些設定套用至你的帳號。

umask 能讓檔案具備可執行權限嗎?

不能。遮罩只能清除建立程式要求的權限位元。touch 從不要求可執行位元,因此任何遮罩都無法產生可執行檔案。使用 ( umask a=rwx; touch f; stat -c '%a %A' f ) 在暫存目錄中確認這一點。若要加入可執行位元,必須使用 chmod,或使用建立檔案時會要求該位元的程式,例如 install -m

systemd 服務的 umask 應在哪裡設定?

在 unit 的 [Service] 區段中使用 UMask=。服務由服務管理員啟動,而不是由登入程序啟動,因此不會讀取 shell 啟動檔案,pam_umask 也不會對其執行。執行 systemctl daemon-reload 並重新啟動該 unit 後,從外部確認結果:讓服務建立檔案,再使用 stat -c '%a %n' 讀取結果。

群組可寫入的預設遮罩安全嗎?

當該群組只有一名成員時是安全的,這正是 user private group 架構的前提。若將第二個帳號加入該群組,第一個帳號建立的每個檔案會立即對新成員變成可寫入,且不需要對這些檔案執行任何命令。執行 id -unid -gn:若輸出相同名稱,表示你使用的是 private group。若多個帳號共用一個群組,請設定會清除群組寫入位元的遮罩,然後建立檔案並讀取 stat -c '%a %n',以確認變更已生效。

#umask#permissions#pam#login-defs#linux