パーミッション

Table of Contents

パーミッションの表し方

パーミッションの表現形式には、オクテット(8進数)形式:

775

と、シンボル形式:

rwxrwxr-x

の2種類がある。左から「オーナー」、「グループ」、「その他」 に許された権限を表す。

通常ビット:
オクテット シンボル 意味
4 r 読み取り可能
2 w 書き込み可能
1 x 実行可能

オクテット形式の時には、これらの数字を足し算した結果で表す。最初に挙げたふたつの例は等価だ。ファイルとディレクトリではパーミッションの働き方がかなり違うので、ほぼ別個の仕組みとして理解しておいたほうがいい。

特殊ビット

オクテット形式では

4777

のように一番左にもうひとつ数字を足して表す。シンボル形式では

rwsrwSrwt

のように実行ビット (x) の位置を置き換える。

オクテット シンボル 意味
4 「ユーザ」に S セットUID (setuid)。シンボルの場合で x も立っている時は s (小文字) で表される
2 「グループ」に S セットGID (setgid)。シンボルの場合で x も立っている時は s (小文字) で表される
1 「その他」に T スティッキー (sticky)。シンボルの場合で x も立っている時は t (小文字) で表される。ディレクトリに対する指定で、且つ「その他」にしか意味を成さない

パーミッションの設定方法

パーミッションを変更できるのはそのオブジェクトの所有者 だけだ。パーミッションを変更するには

chmod perm file_or_dir

するわけだが、perm の部分の書き方にも、オクテット形式とシンボル形式がある。例:

オクテット形式 シンボル形式
chmod 2755 this_file chmod g+xs this_file

オクテット形式には疑問の余地はなかろう。下表は chmod でシンボル形式を使う際の注意点:

記述 意味
g 「グループに」。ユーザなら u 、「その他」なら o 。「すべてに」なら a または省略する。また、例えば「ユーザとグループ」に同じ変更を掛ける場合には ug+xs のような表し方もできる
+ 「追加する」。剥奪するなら - とする。 = を使ってオクテット形式のように絶対指定することもできる
xs 「実行権限と SET*ID を」。 ls 出力の小文字 s は、このように xs に分解して書かなければならない。 t (スティッキービット) も同様
s 「SET*ID を」。 ls の表示では大文字の S だが、chmod は特殊ビットの大文字は理解しないので小文字 s で。 T も同様

なお、シンボル形式でも、perm をカンマで区切れば、ユーザやグループのパーミッションに関する個別の操作を一度に行うことができる。例:

chmod u+x,g-w,o=r this_file

`ls -l' で表示されるパーミッション表現と chmod の引数に指定するパーミッションは書式が異なるので、シェルスクリプトを書く時に困ることがある。例えばファイルA のパーミッションを調べてそれと同じパーミッションをファイルB に対して設定したいような時だ。筆者は、これに対処するために GAWK を使って`ls -l' の出力を chmod の形式に変換するこんなルーティンを書いたことがある。

ファイルのパーミッション

ファイルに対する「読み」「書き」「実行」に関しては誰も説明は要らないだろう。ただ、ひとつ非常に重要なポイントは、UNIXでは、ファイルやディレクトリに書き込み権限がなくてもそれ自体を削除できるということだ。ファイルやディレクトリを削除できるかどうかは、直上のディレクトリのパーミッションが支配する。

ファイルの特殊ビット

ファイルの setuid

実行バイナリファイル (シェルスクリプトは不可 ※コラム参照) に setuid ビットを立てておくと、誰がそれを実行しても、ファイルの所有者のUIDで走る。

ファイルの setgid

実行バイナリファイルに setgid ビットを立てておくと、どんなグループに属するユーザがそれを実行しても、ファイルに設定されたグループのGIDで走る。

シェルスクリプトのsetuidについて

今どきの Linux ディストリビューションでは、シェルスクリプトは setuid ビットが無視されるようになっている。 setuid、特に setuid-root したシェルスクリプトには重大な隙があるからだ。シェルスクリプトを常に特定のユーザの権限で走らせるには、頑張って Cプログラムで書くのがひとつ。もうひとつの手として、特定のスクリプトをラップする小さな Cプログラムを間に挟んでやる方法 (HPのドキュメント) がある。


ディレクトリのパーミッション

ディレクトリのパーミッションを理解しようとする際には、ファイルのパーミッションのことはおおかた忘れたほうがいい。でないとかえって混乱する。ディレクトリのパーミッション動作を呑み込むコツは、どのビットが立っていたら何が「できるか」ではなく、どのビットがなかったら何が「できないか」と考えることだ。ここで言う「内容物」は、そのディレクトリ内のファイルやサブディレクトリなどといったオブジェクトを指す。

~が立っていなかったら どうなるか
x そのディレクトリへ chdir できない。また、内容物を stat() できない。
これら特性の副次的作用として、たとえ rw が立っていたとしても、サブディレクトリ下も含めて、いかなる内容物も追加/削除できないし、書くことも変更することもできないし、実行ファイルを実行することもできない
w このディレクトリ直下に新たな内容物が作れないし削除もできない (x とは異なり '直接的' な効果)。
ただし、x さえ立っていれば、権限のある既存の 内容物を変更/更新することはできる。例えば、w が立っていなくて x の立っているディレクトリの中にある、すべてのビットの立ったフォルダ内では、内容物の作成/削除とも可能だが、そのフォルダ自体を削除することはできない
r そのディレクトリの内容物を一覧表示 (つまり ls) できない。
ただし、x さえ立っていれば、内容物それぞれを名指しして stat や ls -l することは可能。r が影響するのは、どういう名前のオブジェクトがあるかという単純なリストであって、サイズ、オーナー、更新日付などの属性情報は x が支配する

ビット組み合わせ時の考察

これらを組み合わせた場合の効果はじっくり考えれば分かるが、より明確にするため、組み合わせ毎の状態をリストアップしてみよう。自ずと、今度は「立っていなかったら」でなく、「立っていたら」ということになる。例として下図のようなディレクトリを想定する。単純化するために、ここではオーナービットだけを考慮する。

カレントディレクトリ
         test  <問題としているディレクトリ>
            some.txt     rwx(7)
             somedir      rwx(7)
                  another.txt rwx(7)

テスト項目: ls= 'ls test', cd= 'cd test', rm, cat は直下の内容物を削除、閲覧できるか。作成は test 直下に新たなファイル/ディレクトリを作れるか。更新は test 直下の既存のファイル/ディレクトリに変更を加えられるか。実行は test 直下の既存の実行ファイルを実行できるか。なお、200(-w-)300(-wx) は滅多に使われないので考察対象から外す。

test/の
ビット状態
ls cd rm cat 作成 更新 実行 解説
400
(r--)
× × × × × × ls はできることはできるが、内容物の名前が分かるだけで、各々のパーミッションやサイズは分からない。そのため、'cat test/some.txt' のように名指したとしても読むことができない。下層フォルダ somedir に対しても同様
600
(rw-)
× × × × × × 上記とまったく同じ状態
100
(--x)
× × × test 自体は ls できず、直下にオブジェクトを作ることも不可能。ところが、下層の内容物名指しで 'cat some.txt' や 'ls -l somedir' は可能で、 some.txt の更新もできる。サブディレクトリ somedir 内の扱いは somedir 自体の設定に任され、考察の前提とした rwx 状態では、somedir の中に内容物を新たに作ったり、 another.txt を削除することまでできる
500
(r-x)
× × 単純に w が立っていないことによる効果が現れているだけ。下層の somedir の内容物に対する扱いは somedir ディレクトリ自体の設定に完全に委ねられる。ただし、somedir ディレクトリ自体は削除できない

混乱を避けるため触れなかったが、再確認しておくべき重要事項は、test ディレクトリ自体を削除できるかどうかは test 自体のパーミッションビットにはまったく依存せず、さらにひとつ上の親ディレクトリのパーミッションで規定されるという点だ。

ファイルの管理構造とディレクトリパーミッション

Linuxカーネルの内部コードから見ると、ファイル/ディレクトリのパーミッションは、デントリ (dentry = ディレクトリエントリ) と inode (アイノード = index node) を操作するルーティンに直結している。

UNIX/Linux (実際はWINDOWSも含めて現代的なOSならほとんど) では、ディスクに書き込まれているデータを抽象化して扱うため、ファイルの取り扱いを、VFS (バーチャルファイルシステム) という仕組みで包み込んでいる。我々ユーザが、ファイルとかディレクトリという単位で簡単にデータを扱うことができるのは、 この VFS のおかげだ。Linux の VFS では、[ディスク上の実際のデータブロック] => [inode構造体] => [デントリ構造体] => [ファイルやディレクトリ] という連携で、ディスク上のデータと「見た目」のファイルやフォルダとを結びつけている。

まず inode は、ファイルにしろディレクトリにしろ、ひとつひとつに必ず1個、ディスク上に作成される管理情報。この「カード」は、実際にデータが書き込まれているディスク上のブロックと、ファイル/ディレクトリを結びつけるのが一番の役割だが、statls -l コマンドで見られるオーナーやグループ、パーミッションビット、サイズ、最終更新日時など、そのファイル/ディレクトリの属性情報も、この inode が持っている。

しかし、inode だけだったらファイルは孤立無援の存在で、ディレクトリを使って階層的に配置/管理することはできない。その役目は、パス (path) 情報の入れ物であるデントリが担っている。デントリも、ファイルやディレクトリに対して (各 inode に対して、と言ったほうが正確) 各1個ずつ作られる (ハードリンクなど例外もあるが)。ただし、inode とは異なり、場所はメモリ上で、システムが起動してから初めてそのファイル/ディレクトリの参照が要求された時に作成される。次回また参照要求が来た時に、キャッシュとして働き、検索を高速化するのがデントリのそもそもの存在意義だからだ。デントリに「記載」されているのは、対応する inode 番号、親デントリ (つまり親ディレクトリ) へのポインタ、そしてファイル/ディレクトリ名などで、ディレクトリの場合は自分の直下にあるファイルやディレクトリの inode 番号付きリストも持っている。

ls -l した時の動作を考えると、これらの連携が理解しやすい。例えば testdir というディレクトリに ls -l を掛けたとしよう。カーネルはまず、この名前のデントリがあるかどうか調べ、なければ直接 inode を検索した結果からデントリを作成する。この時点で、追加情報無しの子リストはデントリ自体から獲得できる。しかし今回は -l オプションも指定しているので、カーネルは子ファイル/サブフォルダの inode を照会し、それぞれのオーナー、グループID、パーミッションビット、更新日時などを得る、というわけだ。

詰まるところ、ディレクトリのパーミッションビットは、デントリとの関係性で捉えると、じつにスッキリするし、むしろこちらの解釈のほうが本筋に近い。つまり、各ビットの意味は以下のように解釈できる:

x デントリにアクセスして inode へのポインタをたどれるかどうか
w デントリの書き換えができるかどうか
r デントリの内容をカーネル外へ読み出せるかどうか

もっと探りたいなら ext2 File System (pdf/日刊アスキー24), 日経ソフトウエア99年9月号特集1補足記事, @IT:VFSとファイルシステムの基礎技術 あたりが参考になるだろう。Linux のカーネルソースファイルの中でパーミッションに絡むものとしては include/linux/fs.h, fs/namei.c, fs/open.c, fs/exec.c など。デントリの定義は include/linux/dcache.h にある。


ディレクトリの特殊ビット

ディレクトリの setgid

このディレクトリの中に新たな内容物が作られる時、そのオブジェクトは、このディレクトリに設定してあるグループIDを継承する。setgid ビットそのものも継承される。

ディレクトリの setuid

意味無し。一部のホームページには、新規内容物のオーナーIDがディレクトリのオーナーと同じになるようなことも書いてあるが、どう実験してもそうはならない。そういう実装方法を採っている UNIX系OS もあるのかもしれないが、今のところ不明。

stickyビット

スティッキービットは、UNIX の中でも Linux だけに特有の機能らしい。これはディレクトリの「その他 (others)」に対してのみ適用される制約で、 /tmp ディレクトリや samba のパブリックフォルダのように万人に読み書きの許されたディレクトリに設定することが多い。すべてのビットと sticky ビットの立ったディレクトリ内に taro というユーザが内容物を作ると、このオブジェクトは万人が読み書きできるが、オブジェクトの削除は taro 本人かディレクトリのオーナーにしかできない。