22

Note: This could also fit in superuser.

I am setting up PHP 5.3.10 on a shared host with apache2 mpm itk and open_basedir in a way, that each user may not see or change the files of another user. In the apache2 vhost settings, I add the appropriate entries to restrict the user:

    AssignUserId     userA userA
    php_admin_value  open_basedir      /home/userA/www/
    php_admin_value  upload_tmp_dir    /home/userA/www/tmp/
    php_admin_value  session.save_path /home/userA/www/tmp/
    SetEnv           TMPDIR            /home/userA/www/tmp/

Now, the first line sets the linux user to use for apache2, the next three lines define the basedir, upload directory and session savepath to be in the user directory. I'll get back to the last line in a sec.

Now for the problem: sys_get_temp_dir() should give back the temporary directory for php, which is /tmp be default on a linux system. For security reasons, this directory should reside in the open_basedir of userA. According to the php-source of 5.3.10, the sys_get_temp_dir()-function uses the environment variable TMPDIR to get this directory:

     // php-src/main/php_open_temporary_file.c:217-219
     /* On Unix use the (usual) TMPDIR environment variable. */
     {
             char* s = getenv("TMPDIR");

This is what the fifth line in the configuration above should do. However, sys_get_temp_dir() simply returns the global system directory, ignoring the environmental variable (which is perfectly set in $_SERVER, also viewable via phpinfo()).

This results in some nasty bugs with various software relying on sys_get_temp_dir(), as that directory is outside of the open_basedir setting. I've tried to set the variable directly into $_ENV and $_SERVER without a change in behaviour. I've tried a putenv('TMPDIR=/home/userA/www/tmp') without change.

However, I am able to change the output by defining the variable into /etc/apache2/envvars - which is useless for me, as I want each VHOST to have its own temporary folder.

The only solution I have found so far is overwriting the internal sys_get_temp_dir() through an extension like runkit and enforcing its inclusion via auto_prepend_file. But that solution is so dirty, I simply can't believe, that there is no better solution around.

So, my question: Is there any way to change the result of sys_get_temp_dir() to be set in an apache2 vhost setting, without reimplementing the function with runkit?

Edit: The apache version is 2.2.22, and I currently use mod_php. As I will have to add all users manually, an fcgi or similar setup would also be possible.

Lars
  • 5,757
  • 4
  • 25
  • 55
  • Which Apache version are you using? 2.0? 2.2? 2.4? – hakre Nov 07 '12 at 11:45
  • 1
    Just be informed that ``open_basedir`` can be REALLY expensive. I have seen situations where page load times of a complex CMS backend (TYPO3) increased from 3 seconds to >50 seconds under open_basedir protection. – Jpsy Nov 12 '12 at 17:17

7 Answers7

23

Running a putenv('TMPDIR=/foo/bar') inside PHP seems to be able to affect the result of sys_get_temp_dir(). You could have an auto_prepend_file directive arranged to run a piece of PHP to set up the TMPDIR and avoid messing with a redefinition of sys_get_temp_dir().

Edit: Also, you could easily use putenv('TMPDIR='.ini_get('open_basedir').'/tmp') to set the temporary directory to the directory structure you laid out in the question.

Funny enough, this turns out to also work (given that you keep the SetEnv TMPDIR /foo/bar in your Apache configuration):

putenv('TMPDIR='.getenv('TMPDIR'));

Seems like a no-op, but actually does have effect on sys_get_temp_dir(). I'm starting to suspect this has to be some environment-handling bug in PHP.

lanzz
  • 42,060
  • 10
  • 89
  • 98
  • It doesn't for me (as I've written in the question and just rechecked). If it does for you, could you please specify the php- and apache-version you are using? – Lars Nov 06 '12 at 21:37
  • I've got Apache 2.2.17 & PHP 5.3.16 running. Test code: ` – lanzz Nov 06 '12 at 22:25
  • Nothing has changed with this function since my 5.3.10 - what OS are you testing on? – Lars Nov 06 '12 at 23:58
  • 2
    Okay, found the error. I've been using the following testcode: ``. However, php caches the output of `sys_get_temp_dir()` (strangely also unaffected by a `clearstatcache(true, true)`). So, using your method, I would still need to inject a php-script up front. As each user has to get his own, I either have to give each user a custom php.ini via the local php.ini policy or build a function to find the user and replace the builtin `sys_get_temp_dir()`. Thanks for the answer so far! – Lars Nov 07 '12 at 08:29
  • 3
    Can't you just do `putenv('TMPDIR='.ini_get('upload_tmp_dir'))` for all users? `upload_tmp_dir` should already be set at that point. Or `'TMPDIR='.ini_get('open_basedir').'/tmp'`, to be most semantically correct. – lanzz Nov 07 '12 at 08:39
  • that's a great idea - would save me the trouble of regexp'ing for the username! Thanks for everything so far - I'll still leave the question open for other suggestions to come it, because in my gut feeling, there should be more solutions to this problem. – Lars Nov 07 '12 at 08:51
  • @Lars: `clearstatcache` has nothing to do with the cache of the temporary directory path in PHP: http://lxr.php.net/xref/PHP_5_4/main/php_open_temporary_file.c#temporary_directory – hakre Nov 07 '12 at 12:48
  • @hakre is there a way to reset the cache of the temporary directory path then? Namely `PG(php_sys_temp_dir)` in `php_open_temporary_file.c`: https://github.com/php/php-src/blob/49d9b3013fb2ee18b59694aa36036104a4bdd6f2/main/php_open_temporary_file.c#L211. – Ohad Schneider Aug 21 '17 at 11:53
6

You have tagged your question , however you are making use of

 php_admin_value  open_basedir      /home/userA/www/
 ^^^^^^^^^^^^^^^

which is a setting for the apache module version of PHP, Mod_PHP. In that case PHP is loaded once the webserver starts.

Then you make use of SetEnv:

 SetEnv           TMPDIR            /home/userA/www/tmp/

this is setting an internal environment variable. It is passed to other apache modules, however I think you need it with the request, not with the virtual server. I don't know it specifically, but I would assume according to your description that this environment variable is getting reset before the PHP script is invoked.

So more a comment than a real answer, but hopefully it helps you clarify some things.

I normally use FCGI for multi-user environments so that I can better separate the users. I never had problems with setting environment variables per each user. But that's just another comment, I don't want to say you have to use it, too. Just to highlight that you need to find out the right place within apache to set the environment variable so it is (still) set when the script is executed.


Also you might not be setting the right environment variable. According to Apache Documentation about environment variables:

Although these variables are referred to as environment variables, they are not the same as the environment variables controlled by the underlying operating system. Instead, these variables are stored and manipulated in an internal Apache structure. They only become actual operating system environment variables when they are provided to CGI scripts and Server Side Include scripts. If you wish to manipulate the operating system environment under which the server itself runs, you must use the standard environment manipulation mechanisms provided by your operating system shell.

You want to set the operating system environment variable for PHP. But you are setting the internal environment variable only.

Mod_PHP might import them to the script, so if you use getenv('TMPDIR') the PHP SAPI specific implementation is used - which does allow you to see those internal environment variables - however the php_get_temporary_directory function is not using it - it looks like.

Please add your Apache and PHP version to your question.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • 1
    This is obviously a `mod_php` scenario, as otherwise no module would handle the `php_admin_value` directives and Apache would complain about invalid directives in its configuration. – lanzz Nov 07 '12 at 11:29
  • 1
    Which is what I've written, yes a `mod_php` scenario. The interesting question is, how to set an environment variable with Apache that is being reflected by a PHP script that is invoked via `mod_php`. – hakre Nov 07 '12 at 11:41
  • 1
    The environment variable _is_ available inside the `mod_php`-executed PHP script; there is some apparent mishandling of that variable though — `getenv('TMPDIR')` returns the correct value as assigned in Apache, but `sys_get_temp_dir()` only sees the correct value after doing `putenv('TMPDIR='.getenv('TMPDIR'))` – lanzz Nov 07 '12 at 12:24
  • Well PHP is putting [the temporary directory in a static variable](http://lxr.php.net/xref/PHP_5_4/main/php_open_temporary_file.c#temporary_directory). I'm not so sure if your `putenv` will resolve that. Were you able to reproduce what the OP describes in the question? – hakre Nov 07 '12 at 12:47
  • 1
    Yes, and I have tested the suggestions in my answer. This is not a static issue, as it _is_ possible to override the value of the `TMPDIR` variable _before_ the first call to `php_get_temporary_directory`. The issue is that `getenv('TMPDIR')` _in PHP_ returns a different value (the correct one) than the `getenv("TMPDIR")` at line 219 in `php_open_temporary_file.c` (which returns an incorrect value), _unless_ `putenv('TMPDIR='.getenv('TMPDIR'))` has been called _in PHP_ first. – lanzz Nov 07 '12 at 12:51
  • 1
    It could be that apache needs to export the *internal environment variable* from `SetEnv` to the *system environment variable* first. I can not reproduce the issue here, but that might be the case. – hakre Nov 07 '12 at 12:52
  • 1
    No, because _in PHP_, `getenv('TMPDIR')` has no problems to retrieve the value defined with `SetEnv` in the Apache configuration, i.e. it is correctly exported and accessible inside the PHP script. The issue is with `getenv` _in C_ not being able to access that same value. – lanzz Nov 07 '12 at 12:54
  • 1
    @lanzz: Must not be the case. If `getenv` is able to read out also internal apache environment variables - as per the SAPI implementation - then it would read the SetEnv variable out, however that is not a system environment variable then. – hakre Nov 07 '12 at 13:07
  • @hakre I added the apache2 version (2.2.22) in the question. Thanks for the interesting discussion in the comments. However, so far lanzz answer to the question seems to be the most viable setup. I have not asked for privilegue separation due to implementing quota limits yet, as I thought of it as an overkill for the question - should have done that in retrospective. – Lars Nov 10 '12 at 09:53
  • Well I'd still say, the interesting part would be how to make apache configure that system variable so that it is injected in/for PHP as PHP module. – hakre Nov 10 '12 at 10:04
5

According to this - 4 year old - bug, sys_get_temp_dir() won't work with virtual-hosts; so

  • you can try to use only libraries that fixed this issue (& open a bug for those who didn't)
  • or append /tmp (or whatever your OS uses) in your open_basedir, as it can hold multiple directories (like include_path - separate it with ; on Windows, : otherwise)
pozs
  • 34,608
  • 5
  • 57
  • 63
  • thanks for your answer - unfortunately, the multiple directory option did not work for me as well. I forgot to mention this possible solution in the question. – Lars Nov 10 '12 at 09:48
  • 1
    @pozs And now it's *finally* fixed :) – user11153 Apr 26 '13 at 07:35
4

Looking at the PHP source, sys_get_temp_dir() works with the following priority:

  1. If its value has been calculated before, the cached value is used.
  2. sys_temp_dir is checked in the ini configuration.
  3. On Windows, the GetTempPathW Win32 API method is used, and according to its documentation the following are used (in this order):
    1. The path specified by the TMP environment variable.
    2. The path specified by the TEMP environment variable.
    3. The path specified by the USERPROFILE environment variable.
    4. The Windows directory.
  4. In *nix, the following are used (in this order):
    1. The TMPDIR environment variable.
    2. The P_tmpdir macro
    3. /tmp (according to the source, this is a last-ditch effort that should never happen).

That should give you enough options for controlling the result of sys_get_temp_dir (e.g. ini_set('sys_temp_dir', $tmpPath) or putenv('TMPDIR=/foo/bar') as others mentioned) Unless it was previously calculated, in which case you're SOL as far as I know and the cached value will be used (but I have zero knowledge in PHP so would love to hear otherwise).

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
3

This is a bug in php 5.2 - specify temp dir by php.ini
It's fixed in 5.5
Use this as a temporary solution:

<?php
putenv('TMPDIR=/path/to/your/tmp');
          ...your code here ...
eugene
  • 66
  • 4
1

In case people end up here whos Problem is not solved with putenv...

... for me, it worked to set the sys_temp_dir using php's ini_set like this:

$tmpPath = realpath(__DIR__.'/../app/tmp');
ini_set('sys_temp_dir', $tmpPath);

I am running PHP 5.5.9 (cli) on a windows8 machine.

Andresch Serj
  • 35,217
  • 15
  • 59
  • 101
0

It looks like you can change the value returned by sys_get_temp_dir(), I have just tried on apache 2.4 and php 5.6.27.

Add a sys_temp_dir in the virtualhost:

php_admin_value sys_temp_dir "/var/www/alternc/f/fser/tmp"

Restart apache, and print the value in a web page using sys_get_temp_dir():

<?php 
echo sys_get_temp_dir () ;

Produces the expected output: /var/www/alternc/f/fser/tmp.

Aif
  • 11,015
  • 1
  • 30
  • 44