Skip to content

[9.x] Fix Illuminate Filesystem replace() leaves file executable #45856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

kumarravisingh
Copy link
Contributor

@kumarravisingh kumarravisingh commented Jan 29, 2023

The issue #45779 is because of changing the permission of file stored in /tmp directory giving 0775 permission via chmod() method call with umask() subtraction.

chmod($tempPath, 0777 - umask());

Here we are subtracting the umask() value from 0777
i.e 0777 - 002 = 0775 which in permission is rwxrwxr-x i.e others is getting read and execute permission

if we change the permission given to chmod($tempPath, 0776 - umask()); file will have permission
of 0776 - 002 = 0774 which in permission is rwxrwxr-- i.e other is getting read only permission

passing values in test is a bit confusing for me but I tried doing my best :

in testFilePermissionRestoredAfterReplace line 100 I have converted 774 into decimal which is after subtracting 2 of umask() from 776, I had to do the assertion with decimal value since $this->getFilePermissions($tempFile) returns permission value in decimal. ( I think this function name should be getFilePermissionsInDecimal to avoid confusion).

After that I updated the permission value in testReplaceWhenUnixSymlinkExists() test.

@driesvints driesvints changed the title Fixed issue #45779 Illuminate Filesystem replace() leaves file executable [9.x] Fix Illuminate Filesystem replace() leaves file executable Jan 30, 2023
@taylorotwell
Copy link
Member

Changing it to 776 doesn't make much sense to me. From my understanding, you should subtract from 777 for directories and 666 for files. So, changing it to 666 is more sensible to me and fixes the issue on my machine. However, a test is failing where the original test (made by a contributor some years ago) uses a "weird" umask of 131. 666 - 131 is not even a real permission afaik so the test is failing. I'm unsure of the value of this particular test or what it is even trying to test.

I have fixed the test.

@taylorotwell taylorotwell merged commit ce00bd4 into laravel:9.x Jan 30, 2023
@kumarravisingh kumarravisingh deleted the fix-illuminate-filesystem-replace-leaves-file-executable branch January 30, 2023 18:57
@dwightwatson
Copy link
Contributor

Just wanted to flag that this PR appears to be the cause of #45890 preventing Laravel apps from building on Heroku. Not sure exactly if this is an issue with the fix in the PR or an underlying issue with Heroku's PHP build pipeline.

@potsky
Copy link

potsky commented Feb 1, 2023

Just wanted to flag that this PR appears to be the cause of #45890 preventing Laravel apps from building on Heroku. Not sure exactly if this is an issue with the fix in the PR or an underlying issue with Heroku's PHP build pipeline.

Hi @dwightwatson !

We run on an other PAAS than Heroku and we have the same behavior.

@taylorotwell
Copy link
Member

I would like to get to the bottom of this. This change was correct IMO but is apparently causing build issues on PAAS providers. @dwightwatson - what is the value of "umask()" on your Heroku build pipeline? @potsky?

@potsky
Copy link

potsky commented Feb 1, 2023

I cannot say because it is ran during the build and we have no way to know what is really happening. Here is the end of the build log if it can help:

  - Installing voku/simple_html_dom (4.8.7): Extracting archive
   0/237 [>---------------------------]   0%
  30/237 [===>------------------------]  12%
  50/237 [=====>----------------------]  21%
  83/237 [=========>------------------]  35%
 102/237 [============>---------------]  43%
 125/237 [==============>-------------]  52%
 145/237 [=================>----------]  61%
 172/237 [====================>-------]  72%
 196/237 [=======================>----]  82%
 219/237 [=========================>--]  92%
 237/237 [============================] 100%
Generating optimized autoload files
       composer/package-versions-deprecated: Generating version class...
       composer/package-versions-deprecated: ...done generating version class
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
[2023-01-31 17:23:15] production.ERROR: file_put_contents(/build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/bootstrap/cache/packages.php3rSdmw): Failed to open stream: Permission denied {"exception":"[object] (ErrorException(code: 0): file_put_contents(/build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/bootstrap/cache/packages.php3rSdmw): Failed to open stream: Permission denied at /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:212)
[stacktrace]
#0 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(266): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'file_put_conten...', '/build/f7933c11...', 212)
#1 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->Illuminate\\Foundation\\Bootstrap\\{closure}(2, 'file_put_conten...', '/build/f7933c11...', 212)
#2 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php(212): file_put_contents('/build/f7933c11...', '<?php return ar...')
#3 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php(182): Illuminate\\Filesystem\\Filesystem->replace('/build/f7933c11...', '<?php return ar...')
#4 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php(137): Illuminate\\Foundation\\PackageManifest->write(Array)
#5 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php(107): Illuminate\\Foundation\\PackageManifest->build()
#6 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php(90): Illuminate\\Foundation\\PackageManifest->getManifest()
#7 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php(79): Illuminate\\Foundation\\PackageManifest->config('aliases')
#8 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php(26): Illuminate\\Foundation\\PackageManifest->aliases()
#9 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(242): Illuminate\\Foundation\\Bootstrap\\RegisterFacades->bootstrap(Object(Illuminate\\Foundation\\Application))
#10 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(383): Illuminate\\Foundation\\Application->bootstrapWith(Array)
#11 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(153): Illuminate\\Foundation\\Console\\Kernel->bootstrap()
#12 /build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#13 {main}
"} 
       
       In Filesystem.php line 212:
                                                                                      
         file_put_contents(/build/f7933c11-5b28-46c6-97d2-a5d8c78366b3/bootstrap/cac  
         he/packages.php3rSdmw): Failed to open stream: Permission denied             
                                                                                      
       
Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1
!     An error occurred during buildpack compilation
 !   Error deploying the application
 !   → Invalid return code from buildpack 

@taylorotwell I can ask to the PAAS team to make inspections on the build machines if you want. There are very reactive.

I can then add some commands in the build on staging to display outputs like this for example in composer.json :

    "scripts": {
        "post-autoload-dump": [
++            "@php -r \"echo 'UMASK = '.umask();\"",
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"

@stevenmaguire
Copy link
Contributor

what is the value of "umask()" on your Heroku build pipeline?

I can then add some commands in the build on staging to display outputs like this for example in composer.json :

I did exactly that on Heroku CI, here is the result:

CleanShot 2023-02-01 at 09 45 50@2x

UMASK = 63

@stevenmaguire
Copy link
Contributor

FWIW - Heroku offers an interactive shell for the CI session. It is possible to remote in and poke around a bit if that would be useful.

@taylorotwell
Copy link
Member

Thanks for sharing the umask. That's helpful. I'm used to the umask being 22 on most Linux distributions and on macOS. It being 63 leads to some weird permissions when we try to subtract it as you normally would.

@stevenmaguire
Copy link
Contributor

stevenmaguire commented Feb 1, 2023

My macOS shell is currently reporting UMASK = 18

¯\_(ツ)_/¯

@taylorotwell
Copy link
Member

taylorotwell commented Feb 1, 2023

I'm curious if someone can temporarily hardcode Illuminate\Filesystem\Filesystem's chmod call in replace to this:

chmod($tempPath, 0644);

Does Heroku fail then?

@stevenmaguire
Copy link
Contributor

I'll remote in, update the class, then run the discover command, and see what happens.

@potsky
Copy link

potsky commented Feb 1, 2023

UMASK = 63 on Scalingo PAAS too → 077 in octal → so decoct(0777 - 0077); result is 700. I presume on PAAS, owner of directories is not the owner running the build so it fails. Write access for the group is a minimal requirement.

I am going to build with this line in composer.json

"@php -r \"file_put_contents('vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php', str_replace('chmod($tempPath, 0777 - umask());', 'chmod($tempPath, 0644);', file_get_contents('vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php')));\"",

result in a few minutes but I am quite sure it will work.

@taylorotwell
Copy link
Member

OK - my hunch would be to update the replace method to just accept a third argument for the permissions you want on the file after replacement. Then update the only two places we use this framework method to explicitly pass in 0644, which is what all the other files in Laravel are.

@potsky
Copy link

potsky commented Feb 1, 2023

It works

> @php -r "file_put_contents('vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php', str_replace('chmod($tempPath, 0777 - umask());', 'chmod($tempPath, 0644);', file_get_contents('vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php')));"
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
       
          INFO  Discovering packages.  
       
         ashallendesign/short-url .............................................. DONE

@stevenmaguire
Copy link
Contributor

Yeah, Heroku dynos don't ship with a text editor, so I punted and followed @potsky's technique and it built as well.

@taylorotwell
Copy link
Member

Thanks for letting me know.

@potsky
Copy link

potsky commented Feb 1, 2023

@taylorotwell in PackageManifest class, the caller method is:

    protected function write(array $manifest)
    {
        if (! is_writable($dirname = dirname($this->manifestPath))) {
            throw new Exception("The {$dirname} directory must be present and writable.");
        }

        $this->files->replace(
            $this->manifestPath, '<?php return '.var_export($manifest, true).';'
        );
    }

So before replacing or writing the file in $this->files→replace, we check if the directory is writable and it is.

So why not doing something like this in Filesystem class if the goal of the last change on chmod was to leave permissions correctly on the file system ?

        $tempPath = tempnam(dirname($path), basename($path));

        chmod($tempPath, 0644);

        file_put_contents($tempPath, $content);

        // AND THEN RESTORE CORRECT PERMISSIONS 
        chmod($tempPath, 0776 - umask());

nunomaduro added a commit that referenced this pull request Feb 1, 2023
taylorotwell pushed a commit that referenced this pull request Feb 1, 2023
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.

5 participants