Skip to content

Commit 2842048

Browse files
committed
Require explicit --arch or --profile for remote targets
1 parent d894260 commit 2842048

4 files changed

Lines changed: 24 additions & 7 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ default retries because its stage 3 is probabilistic.
6262
- **Version-aware dispatch** — fingerprints the target, rejects fixed releases,
6363
selects the exact build profile and passes offsets, `htdata` and heap
6464
parameters explicitly. `--php-version` covers servers that hide their version;
65-
`--arch` selects aarch64 or amd64 (auto-detected for local containers), and
66-
`--profile` forces an exact build.
65+
`--arch` selects aarch64 or amd64 (auto-detected for local containers,
66+
required for remote targets), and `--profile` forces an exact build.
6767
- **Memory-limit probe** — one request reveals the target `memory_limit` from
6868
the fatal message; the chain then runs once instead of sweeping every value.
6969
- **Worker pinning** — keeps N-1 prefork workers busy with idle keep-alive
@@ -134,7 +134,7 @@ Useful entry points:
134134
```sh
135135
python3 tools/php2shell_auto.py --list # profiles and RCE status
136136
python3 tools/php2shell_auto.py -u <target> --dry-run --no-color
137-
python3 tools/php2shell_auto.py -u <target> --php-version 8.4.26 --allow-remote
137+
python3 tools/php2shell_auto.py -u <target> --php-version 8.4.26 --arch amd64 --allow-remote
138138
python3 tools/php2shell_labs.py status
139139
python3 tools/php2shell_inventory.py
140140
```
@@ -177,9 +177,9 @@ offsets:
177177

178178
- local container: the dispatcher reads `uname -m` and selects the matching
179179
profile automatically;
180-
- remote target: pass `--arch aarch64|amd64` (or `--profile NAME`). If the
181-
version has several builds and no architecture is supplied, the dispatcher
182-
refuses instead of guessing.
180+
- remote target: `--arch aarch64|amd64` (or `--profile NAME`) is **required**,
181+
even when the version has a single profile. HTTP does not expose the CPU
182+
architecture, so the dispatcher refuses instead of guessing.
183183

184184
## Configuration requirements
185185

‎docs/reproducibility.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ profile that only leaks is stored with `rce: false`.
133133
| HTTPS stage 1 fails | use HTTP for the verified path; HTTPS is not end-to-end verified |
134134
| `no proof yet` | `/out` mount, writable webroot, served output path |
135135
| stage 1 fails on a multi-worker target | `--pin-workers N` (remote pinning is opt-in) |
136+
| `ARCH REQUIRED` on a remote target | pass `--arch aarch64|amd64` or `--profile NAME`; HTTP cannot reveal the CPU |
136137
| probe inconclusive | `--memory-limit-mb N` or `--no-probe` |
137138
| stage 3 fails | verify the exact build/profile; 8.4.25 needs retries |
138139

‎tests/test_core.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ def test_branch_only(self):
6060
def test_none(self):
6161
self.assertEqual(auto.select([], "8.4.26", "8.4"), (None, "none"))
6262

63+
def test_remote_arch_required(self):
64+
self.assertTrue(auto.remote_arch_required(True, None, None))
65+
self.assertFalse(auto.remote_arch_required(True, "amd64", None))
66+
self.assertFalse(auto.remote_arch_required(True, None, "p"))
67+
self.assertFalse(auto.remote_arch_required(False, None, None))
68+
6369
def test_select_by_arch(self):
6470
profiles = [
6571
{"name": "p-arm", "php_version": "8.5.11", "branch": "8.5", "arch": "aarch64"},

‎tools/php2shell_auto.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ def load_profiles(directory: str):
180180
return profiles
181181

182182

183+
def remote_arch_required(remote: bool, arch: str | None, profile: str | None) -> bool:
184+
"""Remote targets cannot be fingerprinted for CPU architecture."""
185+
return bool(remote and not arch and not profile)
186+
187+
183188
def select(profiles, version: str, branch: str, arch: str | None = None):
184189
def fits(profile):
185190
return arch is None or profile.get("arch") == arch
@@ -544,7 +549,7 @@ def main() -> int:
544549
ap.add_argument("--pin-max", type=int, default=32,
545550
help="max keep-alive connections for remote auto-pinning")
546551
ap.add_argument("--arch", choices=("aarch64", "amd64"), default=None,
547-
help="select the CPU architecture; auto-detected for local containers")
552+
help="CPU architecture; required for remote targets, auto-detected locally")
548553
ap.add_argument("--profile", default=None,
549554
help="force a profile by name/path substring (needed when a version has several builds)")
550555
ap.add_argument("--no-color", action="store_true")
@@ -594,6 +599,11 @@ def main() -> int:
594599
if detected_arch:
595600
args.arch = detected_arch
596601
ui.info(f"detected architecture: {detected_arch}")
602+
if remote_arch_required(remote, args.arch, args.profile):
603+
ui.crit("remote target: CPU architecture cannot be detected")
604+
ui.info("pass --arch aarch64|amd64 (and --php-version if needed) or --profile NAME")
605+
ui.result(False, "ARCH REQUIRED", "remote target without --arch/--profile")
606+
return 3
597607

598608
version, server, powered = fingerprint(args.target)
599609
if args.php_version:

0 commit comments

Comments
 (0)