- Project: mod_perl 2.0 (
mod_perl2), embeds the Perl interpreter into Apache httpd - Version binding:
2.000014(dev), commitead5012b, 2024-02-06 - Framework: potiuk gist
da14a826283038ddfe38cc9fe6310573 - Status: draft (simple), 0 maintainer review
- Provenance legend: (documented) project docs/README · (maintainer) confirmed by maintainer · (inferred) reasoned from code, not yet verified
- Confidence: ~6 documented / 0 maintainer / ~14 inferred
- One-paragraph description: mod_perl is an Apache httpd module that embeds a persistent Perl interpreter into the server process. The trusted server operator configures Perl handlers (via
Perl*Handlerdirectives,<Perl>config sections, orModPerl::RegistryCGI-style scripts) that run inside each request's lifecycle phases with full server privileges. mod_perl's job is to marshal the Apache request (request_rec, header/env tables, body bucket-brigades) safely into Perl and back.
Persistent in-process Perl handlers inside Apache httpd, replacing fork-per-request CGI. (documented)
| Component family | Representative API | External touchpoints | In model? |
|---|---|---|---|
| Request-data marshaling | $r->headers_in, %ENV, $r->subprocess_env |
HTTP request headers/env | Yes |
| Body/filter I/O | $r->read, $filter->read, APR::Brigade |
HTTP request body | Yes |
| Handler dispatch | Perl*Handler, call_sv |
httpd.conf (operator) | Yes (config = trusted) |
| Registry script runner | ModPerl::Registry, PerlRun |
filesystem (Apache-mapped path) | Yes (boundary), No (script contents) |
<Perl> config sections |
Apache2::PerlSections |
httpd.conf | No (operator-only, startup) |
| Build/glue tooling | ModPerl::WrapXS, Apache2::Build, xs/maps |
build host | No |
- The operator's Perl handler code and Registry script contents are fully trusted; their bugs are not mod_perl vulnerabilities. (inferred) This is the standard "runs trusted code by design" carve-out.
- Perl interpreter and Apache/APR core bugs (upstream, not mod_perl).
- Non-default
RegistryCookersubclasses that re-aliasnamespace_from => namespace_from_uri. - Build/test tooling (
ModPerl::*,Apache2::Build,t/).
The boundary is the HTTP request. Everything in request_rec that derives from the client (headers, query string, body, cookies, CGI env) is untrusted; httpd.conf, <Perl> sections, and handler/Registry files are trusted operator inputs. (inferred)
The request can select which configured handler or Apache-mapped file runs (via Apache's normal <Location>/<Directory>/<Files> matching, done before mod_perl runs) but never supplies code. (inferred, confirmed in dispatch review)
- Runs inside an Apache MPM (prefork / worker / event); persistent interpreter per process/thread. (documented)
- Perl built with or without
ithreads(PERL_IMPLICIT_SYS); threaded MPMs require a thread-safe Perl. (documented, README) - Relies on the APR table invariant: table keys/values are NUL-terminated C strings. (inferred)
| Knob | Default | Effect on model | Posture |
|---|---|---|---|
| DSO vs static | DSO | link shape only | both supported |
| Threaded MPM + ithreads Perl | platform | interpreter pooling (modperl_interp / modperl_tipool) |
supported |
PerlTaintCheck / PerlSwitches -T |
off | taint-tracks request data into handlers | operator must enable if handlers touch untrusted data (documented) |
Options ExecCGI |
off | required before Registry will compile a file | operator-granted (documented, RegistryCooker.pm:258-277) |
LimitRequestBody / LimitRequestFields (Apache) |
Apache defaults | bounds body/header sizes mod_perl will marshal | mod_perl adds no cap of its own (inferred) |
| Path / API | Source | Attacker-controllable? | Caller must enforce |
|---|---|---|---|
%ENV, $r->subprocess_env, $r->headers_in |
request headers / CGI vars | Yes | size/count limits via Apache |
$r->read($buf, $len) |
request body | Yes (content); $len is handler-chosen |
don't pass attacker-derived $len unbounded |
$filter->read($buf, $len) |
brigade body | Yes | same |
$r->filename (Registry) |
URI → Apache translation | path is Apache-mapped (trusted-as-resolved) | ExecCGI, DocumentRoot hygiene |
Primary: remote, unauthenticated HTTP client sending crafted headers/body/URIs. (inferred) Goal: memory corruption in the C marshaling layer, or code execution via injected bytes.
Out of scope: the server operator, the handler author, co-located processes, anyone who can edit httpd.conf or deploy files (all trusted).
(all inferred from code)
- Memory-safe marshaling of request data into Perl, given the APR NUL-termination invariant.
- Violation symptom: OOB read/write. Severity: security-critical (CVE).
- No execution of request-derived bytes as code. Handlers/scripts compile only from operator config and on-disk files;
$ris passed as a runtime argument, never concatenated into a compiled string.- Violation symptom: RCE. Severity: critical.
- Body/filter copies bounded by the caller-requested length, with the destination SV grown to fit before each copy.
- Violation symptom: heap overflow. Severity: critical.
- Registry package names are escaped so a request-mapped path cannot inject Perl syntax into the generated
packagestatement.- Violation symptom: code injection. Severity: critical.
- Does NOT sandbox or limit handler/Registry code. It runs with full server privileges; this is by design, not a weakness. (inferred)
- Does NOT bound resource use. No mod_perl-side cap on the number/size of CGI vars copied into
%ENV, nor on$r->readallocation if a handler passes a large$len. CPU/memory exhaustion driven by handler choices or absent Apache limits is not a mod_perl vulnerability. (inferred) - Taint mode is opt-in, not default. mod_perl is not a WAF and does not sanitize request data.
- Treat all handlers / Registry scripts as trusted code; deploy only what you control.
- Set Apache
LimitRequestBody/LimitRequestFields; mod_perl will not. - Enable
PerlTaintCheckif handlers process untrusted input. - Restrict
ExecCGI; never expose Registry over untrusted upload directories. - Never feed request bytes into
eval/system/ backticks in handlers.
- Handler feeding request data into
eval/system→BY-DESIGN: property-disclaimed(§9), not a mod_perl bug. - Using
ModPerl::Registryfor untrusted/uploaded scripts →OUT-OF-MODEL: trusted-input(script contents are §3). - Relying on mod_perl to cap body/header size →
BY-DESIGN(§9); use Apache limits.
Source review of the three attacker-facing surfaces (request-data marshaling, body/filter I/O, handler dispatch) found no memory-safety or code-execution finding that survives the model. Every dangerous-looking pattern is pre-bounded. These are the scanner false positives most likely to reach a security inbox.
| Pattern a scanner flags | Why safe under model | Cite |
|---|---|---|
strlen(key) / newSVpv(val,0) copying request headers/env into Perl with no length check |
Length comes from the APR table NUL-termination invariant (§5), not the wire; Perl owns allocation | modperl_env.c:37-54,608; modperl_util.c:543 |
memcpy(&wb->outbuf[outcnt], buf, len) in output path |
outbuf is a fixed 8 KB array; guarded by flush-if-len+outcnt>sizeof and direct-send-if-len>=sizeof |
modperl_filter.c:255,262,267 |
apr_file_read into newSV(size) slurp with no bound |
size = r->finfo.size (stat, operator filesystem, not request body); post-read finfo.size != size croak catches 32-bit apr_off_t truncation |
modperl_util.c:616-642 |
apr_pstrmemdup(pv+bucket->start, bucket->length) using raw bucket length |
bucket->start + bucket->length validated against actual SV length before use |
modperl_bucket.c:42-45,77-80 |
SvGROW + sv_catpvn of body bytes in $filter->read |
bounded by caller wanted; SV grown to exact new total before each copy; nibble = wanted - SvCUR protected by loop invariant SvCUR <= wanted |
modperl_filter.c:668-738 |
eval / eval_pv / call_sv near request handling → "RCE?" |
compiles only httpd.conf text + on-disk files; $r passed as argument; package names escaped |
modperl_mgv.c:203; modperl_callback.c:100; RegistryCooker.pm:399-410,680 |
namespace_from_uri(RegistryCooker.pm:340-357) is non-default; it feeds sanitized request-URI bytes into the package name. Still escaped and injection-safe, butOUT-OF-MODEL: non-default-buildrather than impossible. If it became the default, revise.- The
sub {...}anon-handler path string-eval_pvs a directive argument (modperl_mgv.c:203). Operator config today, but any future feature letting request data reach a handler-name string would break §8 property 2. Worth a maintainer question. - New public API, new input format accepted, or a new network surface.
| Disposition | Meaning | Licensed by |
|---|---|---|
VALID |
Violates a claimed property via in-scope adversary and input | §8, §6, §7 |
VALID-HARDENING |
No §8 property violated but API enables §11 misuse; project hardens anyway | §11 |
OUT-OF-MODEL: trusted-input |
Requires attacker control of a trusted parameter (e.g. handler/Registry script contents) | §6, §3 |
OUT-OF-MODEL: adversary-not-in-scope |
Requires an excluded attacker (operator, co-tenant) | §7 |
OUT-OF-MODEL: unsupported-component |
In build/test tooling or <Perl>-section startup path |
§3 |
OUT-OF-MODEL: non-default-build |
Only manifests under a discouraged §5a flag (e.g. namespace_from_uri) |
§5a |
BY-DESIGN: property-disclaimed |
Concerns a property explicitly not provided (sandboxing, resource limits, taint) | §9 |
KNOWN-NON-FINDING |
Matches a documented recurring false positive | §11a |
MODEL-GAP |
Cannot cleanly route to the above | triggers §12 |
Wave 1 (scope and trust):
- Confirm handler/Registry script contents are out of scope (trusted operator code) and that mod_perl's contract is safe marshaling, not sandboxing. Proposed answer: yes. → §3, §9
- Confirm mod_perl intentionally adds no size/count caps of its own on request headers, env, or body, delegating to Apache (
LimitRequestBodyetc.). Proposed answer: yes. → §9, §10 - Is
PerlTaintCheckoff-by-default the supported production posture, with taint expected to be enabled when handlers process untrusted input? Proposed answer: yes, operator's choice. → §5a
Wave 2 (edge cases):
- Is
namespace_from_uriconsidered a supported configuration, or developer-only? Proposed answer: available but not default; sanitized regardless. → §12 - Is the
AP_MODE_READBYTESover-read drop inmodperl_io_apache.c:316-324a known correctness limitation? Proposed answer: yes, not a memory-safety issue. → §8
Provenance tags retained per framework. Versioned alongside mod_perl 2.000014; a report against version N is triaged against the model as it stood at N. Living document; revise on the §12 triggers.