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 open man 2 stat man 7 path_resolution を読むと「誰が・どこで・なぜ弾くか」が見える
  • strace -f -e trace=file,access で権限チェックの順序を追える
  • Android は selinux / seccomp / uid 隔離が重なるので「Linux としての EACCES」+「Android 固有の制約」を分けて考える