POSIX EACCES の契約と振る舞い
タグ: concept posix errno android kernel-contract
EACCES とは
- 意味: Permission denied(アクセス権限なし)
- 定義: POSIX.1-2001 / Linux man
errno(3) - 発生条件: ファイルシステムオブジェクトへのアクセスで、呼び出し元のクレデンシャル(UID/GID/capabilities)が要求操作を許可されていないとき
カーネル側の契約(strace で見えること)
openat(AT_FDCWD, "/path/to/file", O_RDONLY) = -1 EACCES (Permission denied)
statx(AT_FDCWD, "/path/to/file", ...) = -1 EACCES (Permission denied)
open(2)stat(2)access(2)などパスベースのシステムコールで返る- ファイルディスクリプタ操作(
read(2)write(2)等)では返さない——FD取得時点で弾かれるため - ディレクトリの 実行ビット(x) が立っていないと、配下へのトラバース自体が EACCES になる
Android File.exists() の振る舞い
// java.io.File
public boolean exists() {
try {
return Libcore.os.stat(path).st_mode != 0;
} catch (ErrnoException e) {
return false; // EACCES も ENOENT も全部 false
}
}- 内部で
stat(2)を叩いている - あらゆる errno を握りつぶして
falseを返す——EACCES も ENOENT も区別しない - 理由: Java の「例外を投げない API」設計方針 + セキュリティ(存在自体を隠蔽)
自社アプリ側で例外フォールスルーする典型パターン
// 悪い例
val file = File(path)
if (file.exists()) { // ここでは false になるだけ
val input = FileInputStream(file) // ここで FileNotFoundException (EACCES) が飛ぶ
}exists()でfalse→ 「存在しない」と判断 → 別代入路へ- あるいは
exists()を飛ばして直接FileInputStream/Files.readAllBytes等を叩く - unchecked exception(
IOException継承)が上まで伝播 → クラッシュ
判断の指針
| 知りたいこと | 使うべき API | 理由 |
|---|---|---|
| 「読めるか?」 | Files.isReadable(path) / access(2) | 権限チェックのみ。存在は問わない |
| 「存在するか?(権限無視)」 | statx(2) with AT_STATX_DONT_SYNC / Files.exists(path, LinkOption.NOFOLLOW_LINKS) | errno を見れば EACCES と ENOENT を区別可能 |
| 「開けるか?」 | open(2) / FileInputStream を try-catch | 実際の操作で失敗したら諦めるのが正 |
次回へのメモ
man 2 openman 2 statman 7 path_resolutionを読むと「誰が・どこで・なぜ弾くか」が見えるstrace -f -e trace=file,accessで権限チェックの順序を追える- Android は
selinux/seccomp/uid隔離が重なるので「Linux としての EACCES」+「Android 固有の制約」を分けて考える