こんにちは!今回はPHPのPOSIX関数の中から、posix_setegid関数について詳しく解説していきます。プロセスの実効グループID(Effective GID)を設定し、アクセス権限を細かく制御する方法を、実践的なサンプルコードと共にお伝えします。
posix_setegid関数とは?
posix_setegidは、現在のプロセスの**実効グループID(Effective GID)**を設定する関数です。ファイルアクセス権限のチェックに使用されるグループIDを一時的に変更できます。
基本構文
posix_setegid(int $group_id): bool
- 引数: 設定したいグループID(整数)
- 戻り値: 成功時に
true、失敗時にfalse
実グループIDと実効グループIDの違い
Unix/Linuxシステムには2種類のグループIDがあります。
グループIDの種類
<?php
// 実グループID (Real GID)
// - プロセスを起動した実際のユーザーのグループ
// - posix_getgid()で取得
$real_gid = posix_getgid();
// 実効グループID (Effective GID)
// - 実際のアクセス権限チェックに使用されるグループID
// - posix_getegid()で取得
$effective_gid = posix_getegid();
echo "実グループID: {$real_gid}\n";
echo "実効グループID: {$effective_gid}\n";
?>
なぜ2つのIDが必要?
実効グループIDの仕組みにより、一時的に異なるグループの権限でファイルにアクセスできます。
<?php
// 例: ユーザーがグループ'developers' (GID: 1000)で実行
// しかし一時的に 'database' (GID: 2000)グループの権限が必要
$original_egid = posix_getegid(); // 1000
// データベースグループの権限に変更
posix_setegid(2000);
// データベースファイルにアクセス(グループ権限で)
$data = file_get_contents('/var/db/data.db');
// 元の権限に戻す
posix_setegid($original_egid);
?>
実践的な使い方
例1: 基本的な実効GIDの変更
<?php
// 現在の権限情報を表示
function displayGroupInfo() {
$rgid = posix_getgid();
$egid = posix_getegid();
$rgroup = posix_getgrgid($rgid);
$egroup = posix_getgrgid($egid);
echo "実グループID: {$rgid} ({$rgroup['name']})\n";
echo "実効グループID: {$egid} ({$egroup['name']})\n";
echo "\n";
}
echo "=== 初期状態 ===\n";
displayGroupInfo();
// 実効GIDを変更(例: www-dataグループに変更)
$www_data = posix_getgrnam('www-data');
if ($www_data) {
echo "=== www-dataグループに変更 ===\n";
if (posix_setegid($www_data['gid'])) {
displayGroupInfo();
// 元に戻す
echo "=== 元のグループに戻す ===\n";
posix_setegid(posix_getgid());
displayGroupInfo();
} else {
echo "実効GIDの変更に失敗しました\n";
$error = posix_strerror(posix_get_last_error());
echo "エラー: {$error}\n";
}
}
?>
例2: 一時的な権限昇格クラス
<?php
class TemporaryGroupPrivilege {
private $original_egid;
private $changed = false;
public function __construct($group_name_or_gid) {
$this->original_egid = posix_getegid();
// グループIDを取得
if (is_string($group_name_or_gid)) {
$group_info = posix_getgrnam($group_name_or_gid);
if (!$group_info) {
throw new Exception("グループが見つかりません: {$group_name_or_gid}");
}
$gid = $group_info['gid'];
} else {
$gid = $group_name_or_gid;
}
// 実効GIDを変更
if (!posix_setegid($gid)) {
$error = posix_strerror(posix_get_last_error());
throw new Exception("実効GIDの変更に失敗: {$error}");
}
$this->changed = true;
}
public function __destruct() {
// 元の実効GIDに戻す
if ($this->changed) {
posix_setegid($this->original_egid);
}
}
}
// 使用例
try {
echo "通常の権限で実行中...\n";
// 一時的にwww-dataグループの権限に変更
{
$temp = new TemporaryGroupPrivilege('www-data');
echo "www-dataグループの権限で実行中...\n";
// この範囲内でwww-dataグループの権限でファイルにアクセス
// ...
} // スコープを抜けると自動的に元の権限に戻る
echo "通常の権限に戻りました\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例3: セキュアなファイルアクセス
<?php
class SecureFileAccess {
public function readGroupFile($filepath, $group_name) {
// ファイルの存在確認
if (!file_exists($filepath)) {
throw new Exception("ファイルが見つかりません: {$filepath}");
}
// ファイルのグループ所有権を確認
$file_gid = filegroup($filepath);
$group_info = posix_getgrgid($file_gid);
echo "ファイル: {$filepath}\n";
echo "所有グループ: {$group_info['name']} (GID: {$file_gid})\n";
// 元の実効GIDを保存
$original_egid = posix_getegid();
// 指定されたグループの権限に変更
$target_group = posix_getgrnam($group_name);
if (!$target_group) {
throw new Exception("グループが見つかりません: {$group_name}");
}
try {
// グループ権限に変更
if (!posix_setegid($target_group['gid'])) {
throw new Exception("権限の変更に失敗しました");
}
// ファイルの読み取り
if (!is_readable($filepath)) {
throw new Exception("ファイルの読み取り権限がありません");
}
$content = file_get_contents($filepath);
return $content;
} finally {
// 必ず元の権限に戻す
posix_setegid($original_egid);
}
}
public function writeGroupFile($filepath, $content, $group_name) {
$original_egid = posix_getegid();
$target_group = posix_getgrnam($group_name);
if (!$target_group) {
throw new Exception("グループが見つかりません: {$group_name}");
}
try {
// グループ権限に変更
if (!posix_setegid($target_group['gid'])) {
throw new Exception("権限の変更に失敗しました");
}
// ファイルの書き込み
if (file_exists($filepath) && !is_writable($filepath)) {
throw new Exception("ファイルの書き込み権限がありません");
}
$result = file_put_contents($filepath, $content);
if ($result === false) {
throw new Exception("ファイルの書き込みに失敗しました");
}
return $result;
} finally {
// 必ず元の権限に戻す
posix_setegid($original_egid);
}
}
}
// 使用例
$fileAccess = new SecureFileAccess();
try {
// www-dataグループの権限でファイルを読む
$content = $fileAccess->readGroupFile(
'/var/www/data/config.json',
'www-data'
);
echo "ファイル読み取り成功\n";
// データベースグループの権限でファイルに書き込む
$result = $fileAccess->writeGroupFile(
'/var/db/backup.sql',
'BACKUP DATA',
'database'
);
echo "ファイル書き込み成功\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例4: グループベースのアクセス制御システム
<?php
class GroupAccessControl {
private $access_rules = [];
public function defineRule($resource, $allowed_groups) {
$this->access_rules[$resource] = $allowed_groups;
}
public function checkAccess($resource) {
if (!isset($this->access_rules[$resource])) {
return false;
}
$allowed_groups = $this->access_rules[$resource];
$current_egid = posix_getegid();
// 現在の実効グループが許可リストに含まれているか確認
foreach ($allowed_groups as $group_name) {
$group_info = posix_getgrnam($group_name);
if ($group_info && $group_info['gid'] === $current_egid) {
return true;
}
}
// 補助グループもチェック
$groups = posix_getgroups();
foreach ($allowed_groups as $group_name) {
$group_info = posix_getgrnam($group_name);
if ($group_info && in_array($group_info['gid'], $groups)) {
return true;
}
}
return false;
}
public function executeWithGroup($group_name, callable $callback) {
$group_info = posix_getgrnam($group_name);
if (!$group_info) {
throw new Exception("グループが見つかりません: {$group_name}");
}
$original_egid = posix_getegid();
try {
// グループ権限に変更
if (!posix_setegid($group_info['gid'])) {
throw new Exception("実効GIDの変更に失敗しました");
}
// コールバックを実行
return $callback();
} finally {
// 元の権限に戻す
posix_setegid($original_egid);
}
}
public function accessResource($resource, $group_name, callable $action) {
// ルールをチェック
if (!isset($this->access_rules[$resource])) {
throw new Exception("リソースのアクセスルールが定義されていません");
}
if (!in_array($group_name, $this->access_rules[$resource])) {
throw new Exception("グループ '{$group_name}' はリソース '{$resource}' へのアクセス権限がありません");
}
// 指定されたグループの権限で実行
return $this->executeWithGroup($group_name, $action);
}
}
// 使用例
$acl = new GroupAccessControl();
// アクセスルールを定義
$acl->defineRule('/var/www/html', ['www-data', 'developers']);
$acl->defineRule('/var/db', ['database', 'admin']);
$acl->defineRule('/var/log', ['syslog', 'admin']);
try {
// www-dataグループでWebファイルにアクセス
$acl->accessResource('/var/www/html', 'www-data', function() {
echo "Webファイルを処理中...\n";
// ファイル操作
return "処理完了";
});
// databaseグループでDBファイルにアクセス
$acl->accessResource('/var/db', 'database', function() {
echo "データベースファイルを処理中...\n";
// データベース操作
return "バックアップ完了";
});
// 権限のないアクセスを試みる(失敗する)
$acl->accessResource('/var/db', 'www-data', function() {
echo "この行は実行されません\n";
});
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例5: setgidビットのシミュレーション
<?php
class SetgidSimulator {
public function demonstrateSetgid() {
echo "=== setgidビットの動作説明 ===\n\n";
// setgidビットとは
echo "setgidビットが設定されたファイルを実行すると、\n";
echo "実効GIDがファイルの所有グループに設定されます。\n\n";
// 現在の状態
$rgid = posix_getgid();
$egid = posix_getegid();
$rgroup = posix_getgrgid($rgid);
$egroup = posix_getgrgid($egid);
echo "現在の状態:\n";
echo " 実GID: {$rgid} ({$rgroup['name']})\n";
echo " 実効GID: {$egid} ({$egroup['name']})\n\n";
// setgidビットのシミュレーション
echo "setgidファイル実行をシミュレート:\n";
// 例: /usr/bin/wall (通常setgidビットが設定されている)
$wall_stat = @stat('/usr/bin/wall');
if ($wall_stat) {
$wall_gid = $wall_stat['gid'];
$wall_group = posix_getgrgid($wall_gid);
echo " ファイル: /usr/bin/wall\n";
echo " 所有グループ: {$wall_group['name']} (GID: {$wall_gid})\n";
if (($wall_stat['mode'] & 02000) === 02000) {
echo " setgidビット: 設定されています\n";
echo "\n";
echo " このファイルを実行すると:\n";
echo " 実効GID → {$wall_gid} ({$wall_group['name']})\n";
} else {
echo " setgidビット: 設定されていません\n";
}
}
}
public function createSetgidFile($filepath, $group_name, $content) {
// ファイルを作成
if (!file_put_contents($filepath, $content)) {
throw new Exception("ファイルの作成に失敗しました");
}
// 実行可能にする
chmod($filepath, 0750);
// グループ所有権を設定
$group_info = posix_getgrnam($group_name);
if (!$group_info) {
throw new Exception("グループが見つかりません: {$group_name}");
}
chgrp($filepath, $group_info['gid']);
// setgidビットを設定
chmod($filepath, 02750); // 2はsetgidビット
echo "setgidファイルを作成しました:\n";
echo " パス: {$filepath}\n";
echo " グループ: {$group_name}\n";
echo " パーミッション: " . decoct(fileperms($filepath)) . "\n";
return true;
}
}
// 使用例
$simulator = new SetgidSimulator();
$simulator->demonstrateSetgid();
// setgidファイルの作成例(root権限が必要)
// $simulator->createSetgidFile('/tmp/test_setgid.sh', 'www-data', '#!/bin/bash\necho "Running with setgid"');
?>
注意点とトラブルシューティング
権限の制限
<?php
// 一般ユーザーは自分が所属するグループにのみ変更可能
function canChangeToGroup($gid) {
$groups = posix_getgroups();
return in_array($gid, $groups);
}
$target_gid = 1001;
if (canChangeToGroup($target_gid)) {
echo "グループ {$target_gid} への変更が可能です\n";
posix_setegid($target_gid);
} else {
echo "グループ {$target_gid} への変更権限がありません\n";
echo "所属グループ: " . implode(', ', posix_getgroups()) . "\n";
}
?>
setgidとseteuidの順序
<?php
// 権限を降格する場合の正しい順序
function dropPrivileges($uid, $gid) {
// 重要: 先にグループを変更してからユーザーを変更
// ユーザーを先に変更すると、グループ変更の権限を失う
// 1. まず実効GIDを変更
if (!posix_setegid($gid)) {
throw new Exception("実効GIDの変更に失敗");
}
// 2. 次に実グループIDを変更
if (!posix_setgid($gid)) {
throw new Exception("実GIDの変更に失敗");
}
// 3. 実効UIDを変更
if (!posix_seteuid($uid)) {
throw new Exception("実効UIDの変更に失敗");
}
// 4. 最後に実UIDを変更
if (!posix_setuid($uid)) {
throw new Exception("実UIDの変更に失敗");
}
return true;
}
?>
エラーハンドリング
<?php
function safeSetegid($gid) {
$original_egid = posix_getegid();
if (!posix_setegid($gid)) {
$errno = posix_get_last_error();
$error = posix_strerror($errno);
// よくあるエラー
switch ($errno) {
case 1: // EPERM
throw new Exception("権限がありません。グループ {$gid} への変更が許可されていません");
case 22: // EINVAL
throw new Exception("無効なグループID: {$gid}");
default:
throw new Exception("実効GID変更失敗: {$error} (errno: {$errno})");
}
}
// 変更が成功したか確認
$new_egid = posix_getegid();
if ($new_egid !== $gid) {
// ロールバック
posix_setegid($original_egid);
throw new Exception("実効GIDの設定に失敗しました(確認エラー)");
}
return true;
}
// 使用例
try {
$target_gid = 33; // www-dataのGID
safeSetegid($target_gid);
echo "実効GIDを {$target_gid} に変更しました\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
Windows環境での制限
<?php
if (!function_exists('posix_setegid')) {
die("posix_setegidはこの環境では使用できません\n" .
"Unix/Linux/macOS環境で実行してください\n");
}
// クロスプラットフォーム対応
class PlatformGroupManager {
public function setEffectiveGroup($gid) {
if (function_exists('posix_setegid')) {
return posix_setegid($gid);
}
// Windows環境では代替処理
throw new Exception("この環境ではグループ変更がサポートされていません");
}
}
?>
関連する便利な関数
グループID関連関数の完全セット
<?php
// グループID取得
$gid = posix_getgid(); // 実グループID
$egid = posix_getegid(); // 実効グループID
$groups = posix_getgroups(); // 補助グループリスト
// グループID設定
posix_setgid($gid); // 実グループIDを設定
posix_setegid($egid); // 実効グループIDを設定
// グループ情報取得
$info = posix_getgrgid($gid); // GIDからグループ情報
$info = posix_getgrnam('www-data'); // 名前からグループ情報
// グループリストの初期化
posix_initgroups('username', $gid); // 補助グループを初期化
// ユーザーID関連(組み合わせて使用)
$uid = posix_getuid(); // 実ユーザーID
$euid = posix_geteuid(); // 実効ユーザーID
posix_setuid($uid); // 実ユーザーIDを設定
posix_seteuid($euid); // 実効ユーザーIDを設定
?>
実用的な権限管理クラス
<?php
class PrivilegeManager {
private $saved_uid = null;
private $saved_gid = null;
private $saved_euid = null;
private $saved_egid = null;
public function saveCurrentPrivileges() {
$this->saved_uid = posix_getuid();
$this->saved_gid = posix_getgid();
$this->saved_euid = posix_geteuid();
$this->saved_egid = posix_getegid();
}
public function restorePrivileges() {
if ($this->saved_egid !== null) {
posix_setegid($this->saved_egid);
}
if ($this->saved_euid !== null) {
posix_seteuid($this->saved_euid);
}
}
public function getCurrentPrivileges() {
return [
'uid' => posix_getuid(),
'gid' => posix_getgid(),
'euid' => posix_geteuid(),
'egid' => posix_getegid(),
'groups' => posix_getgroups(),
];
}
public function displayPrivileges() {
$priv = $this->getCurrentPrivileges();
$user = posix_getpwuid($priv['uid']);
$group = posix_getgrgid($priv['gid']);
$euser = posix_getpwuid($priv['euid']);
$egroup = posix_getgrgid($priv['egid']);
echo "=== 現在の権限情報 ===\n";
echo "実UID: {$priv['uid']} ({$user['name']})\n";
echo "実GID: {$priv['gid']} ({$group['name']})\n";
echo "実効UID: {$priv['euid']} ({$euser['name']})\n";
echo "実効GID: {$priv['egid']} ({$egroup['name']})\n";
echo "補助グループ: " . implode(', ', $priv['groups']) . "\n";
}
}
// 使用例
$pm = new PrivilegeManager();
$pm->displayPrivileges();
?>
まとめ
posix_setegid関数は以下の特徴があります。
✅ 実効グループIDを一時的に変更 ✅ ファイルアクセス権限の細かい制御 ✅ setgidビットの動作を理解するのに重要 ✅ セキュアなマルチユーザーシステムに必須 ✅ 権限の一時的な昇格/降格に使用
実効グループIDの制御は、セキュアなシステムプログラミングの基本です。適切に使用することで、最小権限の原則に従いながら、必要な時だけ特定のリソースにアクセスできるようになります。
ベストプラクティス
- 必ず元に戻す: try-finallyまたはデストラクタで元の権限に復帰
- 最小権限: 必要最小限の時間だけ権限を変更
- 順序を守る: 権限降格時はGID→UIDの順
- エラー処理: 権限変更の失敗を適切にハンドル
- ログ記録: セキュリティ監査のため権限変更を記録
セキュリティの考慮事項
- 権限エスカレーション: 意図しない権限昇格を防ぐ
- race condition: 権限変更とファイルアクセスのタイミング
- 補助グループ: posix_initgroups()と組み合わせて使用
- 監査ログ: 権限変更を記録して追跡可能に
安全な権限管理を! この記事が役に立ったら、ぜひシェアしてください。PHPのセキュリティとシステムプログラミングについて、他にも知りたいことがあればコメントで教えてくださいね。
