Skip to content

fix(php): a function this PHP-FPM build does not have is no longer shown as Allowed — GH #1701 - #2002

Merged
shukiv merged 1 commit into
mainfrom
gh1701-fpm-availability
Oct 4, 2026
Merged

shukiv merged 1 commit into
mainfrom
gh1701-fpm-availability

Conversation

@shukiv

@shukiv shukiv commented Oct 4, 2026

Copy link
Copy Markdown
Owner

GH #1701 (lxsdevcode, follow-up on #1990). The read-only Disabled functions and paths section showed pcntl_exec, pcntl_fork and dl as Allowed when the hosting package allowed them. But function_exists() returned false for all three in the site's PHP (8.3, fpm-fcgi). The section read only the configuration (disable_functions, PHP Defense rules), not what PHP-FPM actually provides.

Why those three are missing

  • pcntl_*: Debian builds the pcntl extension into the CLI only. php-fpm8.3 -m doesn't list it, while the CLI resolves pcntl_exec to pcntl (checked on the test box).
  • dl(): the command-line SAPI registers it itself (php-src PHP-8.3 sapi/cli/php_cli.c additional_functions; main/main.c registers them into standard). FPM never does: it sets additional_functions = NULL and adds only its own cgi-fcgi functions (fpm_main_arginfo.h).

What changes

  • Agent (php.pool.effective) returns unavailable_functions:
    • Checked: the lockdown list plus every disabled or PHP Defense-banned name.
    • Method: PHP-FPM's own module list (php-fpm<ver> -m, with the pool's ini layering), plus each function's extension as the CLI reports it with the same layering.
    • SAPI functions: the CLI's own (dl, cli_set_process_title, cli_get_process_title) count as missing. FPM's own (fastcgi_finish_request, apache_request_headers, getallheaders, fpm_get_status) count as present.
    • Never missing: a php.ini-disabled function, because PHP 8 drops it from the function table, so the CLI can't see it.
    • Failure: when either read fails (or php.ini can't be read), availability_error says so and the list stays empty rather than guessed.
    • Safety: function names are passed to PHP as arguments, never interpolated into the script, and only names matching ^[a-z_][a-z0-9_]{0,99}$ are checked.
  • API passes unavailable_functions (always a list, [] when an agent sends none) and availability_error through. A new test fails if the API's typed response struct drops a field the agent sends.
  • UI:
    • A new status, Allowed, not in this PHP build.
    • A note explains the status, with pcntl_ and dl() as examples, and says a later PHP build that includes the function makes it Allowed with no package change.
    • A warning appears when the check couldn't run.
    • Disabled and blocked keep their status. "Not in this build" only qualifies Allowed (and allowed-but-logged), as the reporter suggested.
  • Docs: docs/site/php.md and docs/site/user/php-settings.md.

Verification

  • Real box (.60, PHP 8.4): I dispatched the real php.pool.effective with a throwaway binary (since trashed) against two pools. It returned unavailable_functions: ["dl","pcntl_exec","pcntl_fork"] with no errors, matching the reporter's runtime probe. proc_nice and exec were not listed.
  • Tests:
    • TestUnavailableInFPM (pure decision: CLI-SAPI function, FPM-SAPI function, php.ini-disabled, no such function, extension FPM doesn't load);
    • TestReadPHPPoolEffectiveUnavailable (end to end with stubbed probes, including which names get checked);
    • TestReadPHPPoolEffectiveAvailabilityError, and the existing no-ini test asserting nothing is called unavailable without php.ini;
    • API passthrough, [] normalization, and the agent ↔ API response mirror test;
    • UI precedence and rendering tests.
  • Falsified: each fix was broken in turn, and its tests went red.
    • Dropping the CLI-SAPI set, the php.ini-disabled exclusion, or the FPM module check failed both agent tests.
    • Dropping the API's [] normalization failed the owner test.
    • Dropping the API field failed 3 tests, including the mirror test.
    • Making the UI ignore the list failed 2 UI tests.
  • Ran:
    • go test ./panel-agent/internal/commands/ ./panel-api/internal/api/;
    • go vet;
    • full vitest (157 files, 935 passed);
    • tsc -b and eslint on the touched files;
    • GitNexus detect-changes: 10 files, 0 affected processes, risk LOW. readPHPPoolEffective has one caller.

https://claude.ai/code/session_0173PcNd4h6NceYPc4FuhvXj

…own as Allowed — GH #1701

The reporter found that the read-only "Disabled functions and paths" section
listed pcntl_exec, pcntl_fork and dl as Allowed when the hosting package
allowed them. Yet function_exists() returned false for all three in the
site's PHP (8.3, fpm-fcgi). The section read only the configuration
(disable_functions, PHP Defense rules), not what PHP-FPM actually provides:

- Debian builds the pcntl extension into the CLI only; php-fpm8.3 -m does
  not list it (verified on the test box).
- dl() is registered by the command-line SAPI itself (php-src PHP-8.3
  sapi/cli/php_cli.c additional_functions, registered into "standard"),
  never by FPM.

php.pool.effective now also returns unavailable_functions. It covers the
lockdown list plus every disabled or banned name, checked against PHP-FPM's
own module list (php-fpm -m, with the pool's ini layering) and each
function's extension as the CLI reports it. The CLI SAPI's own functions
count as missing, and FPM's own (fastcgi_finish_request, getallheaders, …)
as present. A php.ini-disabled function is never called missing, since PHP
drops it from the function table. When the check cannot run,
availability_error says so and the list stays empty. Names go to PHP as
arguments, never into the script.

The page shows such a function as "Allowed, not in this PHP build", with a
note on why and that a later PHP build that includes it makes it Allowed
without a package change. Disabled and blocked keep their status;
unavailable only qualifies Allowed (and allowed-but-logged).

The API passes the new fields through (unavailable_functions is always a
list). A test now fails if the API's typed struct drops a field the agent
sends. The operator and user PHP docs describe the new status.

Claude-Session: https://claude.ai/code/session_0173PcNd4h6NceYPc4FuhvXj
@shukiv
shukiv merged commit 189db83 into main Oct 4, 2026
11 checks passed
@shukiv
shukiv deleted the gh1701-fpm-availability branch October 4, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant