Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,35 @@ $operatingSystem = new OperatingSystem();
$operatingSystem->getLinuxDistribution();
```


### SELinux status detection

The `getSELinuxStatus()` method detects the current SELinux enforcement status without requiring administrative privileges.

Usage example:

```php
<?php

use LibreSign\WhatOSAmI\OperatingSystem;

$operatingSystem = new OperatingSystem();
$status = $operatingSystem->getSELinuxStatus();

if ($status === OperatingSystem::SELINUX_ENFORCING) {
// SELinux is enforcing access-control rules.
}
```

Possible return values:

- `enforcing`: SELinux is active and enforcing security policies.
- `permissive`: SELinux is active but not enforcing security policies.
- `unavailable`: The SELinux status file does not exist.
- `unknown`: The SELinux status could not be determined.

The method reads `/sys/fs/selinux/enforce` and does not modify system settings.

## How to contribute

Feel free to fork, improve and I will be very happy to receive pull requests with your improvements.
Expand Down
121 changes: 120 additions & 1 deletion src/OperatingSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,67 @@

class OperatingSystem
{
public const SELINUX_ENFORCING = 'enforcing';
public const SELINUX_PERMISSIVE = 'permissive';
public const SELINUX_UNAVAILABLE = 'unavailable';
public const SELINUX_UNKNOWN = 'unknown';

public function __construct(
private string $rootFolder = '/',
) {
}

/**
* Returns the current SELinux enforcement status.
*/
public function getSELinuxStatus(): string
{
$path = $this->rootFolder . '/sys/fs/selinux/enforce';

// Let PHP resolve the effective path, including symbolic links.
// A blocked open_basedir check emits a warning; keep it local and
// report UNKNOWN rather than confusing restricted access with absence.
$restrictedAccess = false;
if (ini_get('open_basedir')) {
set_error_handler(static function () use (&$restrictedAccess): bool {
$restrictedAccess = true;
return true;
}, E_WARNING);

try {
$exists = file_exists($path);
} finally {
restore_error_handler();
}
} else {
$exists = file_exists($path);
}

if ($restrictedAccess) {
return self::SELINUX_UNKNOWN;
}

if (!$exists) {
return self::SELINUX_UNAVAILABLE;
}

if (!is_readable($path)) {
return self::SELINUX_UNKNOWN;
}

$value = @file_get_contents($path);

if ($value === false) {
return self::SELINUX_UNKNOWN;
}

return match (trim($value)) {
'1' => self::SELINUX_ENFORCING,
'0' => self::SELINUX_PERMISSIVE,
default => self::SELINUX_UNKNOWN,
};
}

/**
* Returns PHP_OS_FAMILY (if defined (which it is on PHP >= 7.2)).
* Returns a string (compatible with PHP_OS_FAMILY) derived from PHP_OS otherwise.
Expand Down Expand Up @@ -134,9 +191,71 @@ private function runSafe(string $command): string
private function isInOpenBasedir(string $path): bool
{
$openBasedir = ini_get('open_basedir');
if (empty($openBasedir) || strpos($openBasedir, $path) !== false) {
if ($openBasedir === false || $openBasedir === '') {
return true;
}

$normalizedPath = $this->normalizePathForOpenBasedir($path);
if ($normalizedPath === null) {
return false;
}

foreach (explode(PATH_SEPARATOR, $openBasedir) as $directory) {
$normalizedDirectory = $this->normalizePathForOpenBasedir($directory);
if ($normalizedDirectory === null) {
continue;
}

if ($normalizedDirectory === '/' ||
$normalizedPath === $normalizedDirectory ||
str_starts_with($normalizedPath, rtrim($normalizedDirectory, '/') . '/')) {
return true;
}
}

return false;
}

/**
* Normalize separators, repeated slashes, and dot segments before checking
* directory boundaries. No filesystem access is performed: PHP can reject
* realpath/file_exists calls outside open_basedir with a warning.
*/
private function normalizePathForOpenBasedir(string $path): ?string
{
$path = str_replace('\\', '/', $path);
if ($path === '') {
return null;
}

$drive = '';
if (DIRECTORY_SEPARATOR === '\\' && preg_match('/^[a-z]:\//i', $path) === 1) {
$drive = strtolower(substr($path, 0, 2));
$path = substr($path, 2);
} elseif (!str_starts_with($path, '/')) {
$cwd = getcwd();
if ($cwd === false) {
return null;
}
$path = str_replace('\\', '/', $cwd) . '/' . $path;
if (DIRECTORY_SEPARATOR === '\\' && preg_match('/^[a-z]:\//i', $path) === 1) {
$drive = strtolower(substr($path, 0, 2));
$path = substr($path, 2);
}
}

$parts = [];
foreach (explode('/', $path) as $part) {
if ($part === '' || $part === '.') {
continue;
}
if ($part === '..') {
array_pop($parts);
continue;
}
$parts[] = DIRECTORY_SEPARATOR === '\\' ? strtolower($part) : $part;
}

return $drive . '/' . implode('/', $parts);
}
}
Loading
Loading