Skip to content

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

Merged
taylorotwell merged 8 commits into
laravel:9.xfrom
kumarravisingh:fix-illuminate-filesystem-replace-leaves-file-executable
Jan 30, 2023
Merged

[9.x] Fix Illuminate Filesystem replace() leaves file executable#45856
taylorotwell merged 8 commits into
laravel:9.xfrom
kumarravisingh:fix-illuminate-filesystem-replace-leaves-file-executable

Conversation

@kumarravisingh

@kumarravisingh kumarravisingh commented Jan 29, 2023

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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

potsky commented Feb 1, 2023

Copy link
Copy Markdown

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
Copy Markdown
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

potsky commented Feb 1, 2023

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

stevenmaguire commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

My macOS shell is currently reporting UMASK = 18

¯\_(ツ)_/¯

@taylorotwell

taylorotwell commented Feb 1, 2023

Copy link
Copy Markdown
Member

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
Copy Markdown
Contributor

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

@potsky

potsky commented Feb 1, 2023

Copy link
Copy Markdown

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
Copy Markdown
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

potsky commented Feb 1, 2023

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
Member

Thanks for letting me know.

@potsky

potsky commented Feb 1, 2023

Copy link
Copy Markdown

@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