Skip to content

ext/phar: Phar::webPhar() 301 redirect keeps PATH_INFO on the CGI SAPIs when a rewrite callback routes to the index #25112

Open

Description

@dhrupo

Description

Follow-up to #25093 (PR #25104), which fixes the non-CGI side of the same Location header. This one is not a memory-safety problem; it produces a wrong redirect target.

Drafted with an LLM; I reviewed it and checked every claim against the source myself.

In Phar::webPhar(), the request path is truncated right after the phar's file name before the entry is appended to the Location header, but only when not_cgi is set:

if (not_cgi) {
    tmp = strstr(path_info, basename) + fname_len;   /* the offset #25104 fixes */
    sa = *tmp;
    *tmp = '\0';
}

On cgi, cgi-fcgi, fpm-fcgi and litespeed, not_cgi is 0 and path_info is SCRIPT_NAME . PATH_INFO, so it is never truncated and the Location keeps PATH_INFO.

It only surfaces when a rewrite callback collapses a non-empty PATH_INFO to "" or "/", which is how a front controller routes every request to the index. webPhar() then takes the direct-request branch, entry becomes /index.php, the entry is found, and a 301 is emitted. Without a rewrite callback the result is already correct: an absent PATH_INFO leaves path_info equal to SCRIPT_NAME, and PATH_INFO of / is absorbed by the trailing-slash branch.

With the phar at /sub/app.phar, SCRIPT_NAME=/sub/app.phar, PATH_INFO=/foo, and a rewrite callback returning "/":

  • Expected: Location: /sub/app.phar/index.php
  • Actual: Location: /sub/app.phar/foo/index.php

The next request arrives with PATH_INFO=/foo/index.php, the callback returns "/" again, and the path grows on each hop, so the client loops.

The function already computes the correct prefix in pt: the _server branch sets it to SCRIPT_NAME, and the other two branches truncate right after the file name. pt is still allocated at the 301, since it is only freed on the bail-out paths above it and after the redirect. Using pt to build the Location would cover every SAPI and remove the strstr/poke/restore around it, but it changes the header on the CGI SAPIs, so it looked worth reporting separately rather than folding into #25104.

I have not written a patch for this one.

PHP Version

Observed by reading ext/phar/phar_object.c on PHP-8.4, PHP-8.5 and master; the if (not_cgi) guard is identical on all three.

Operating System

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions