Coverage Report

Created: 2025-06-13 06:43

/src/php-src/main/php_open_temporary_file.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Author: Zeev Suraski <zeev@php.net>                                  |
14
   +----------------------------------------------------------------------+
15
 */
16
17
#include "php.h"
18
#include "zend_long.h"
19
#include "php_open_temporary_file.h"
20
#include "ext/random/php_random.h"
21
#include "zend_operators.h"
22
23
#include <errno.h>
24
#include <sys/types.h>
25
#include <sys/stat.h>
26
#include <fcntl.h>
27
28
#ifdef PHP_WIN32
29
#define O_RDONLY _O_RDONLY
30
#include "win32/param.h"
31
#include "win32/winutil.h"
32
#else
33
#include <sys/param.h>
34
#include <sys/socket.h>
35
#include <netinet/in.h>
36
#include <netdb.h>
37
#ifdef HAVE_ARPA_INET_H
38
#include <arpa/inet.h>
39
#endif
40
#endif
41
#ifdef HAVE_SYS_TIME_H
42
#include <sys/time.h>
43
#endif
44
45
#ifdef HAVE_SYS_FILE_H
46
#include <sys/file.h>
47
#endif
48
49
#if !defined(P_tmpdir)
50
#define P_tmpdir ""
51
#endif
52
53
/* {{{ php_do_open_temporary_file */
54
55
/* Loosely based on a tempnam() implementation by UCLA */
56
57
/*
58
 * Copyright (c) 1988, 1993
59
 *      The Regents of the University of California.  All rights reserved.
60
 *
61
 * Redistribution and use in source and binary forms, with or without
62
 * modification, are permitted provided that the following conditions
63
 * are met:
64
 * 1. Redistributions of source code must retain the above copyright
65
 *    notice, this list of conditions and the following disclaimer.
66
 * 2. Redistributions in binary form must reproduce the above copyright
67
 *    notice, this list of conditions and the following disclaimer in the
68
 *    documentation and/or other materials provided with the distribution.
69
 * 3. All advertising materials mentioning features or use of this software
70
 *    must display the following acknowledgement:
71
 *      This product includes software developed by the University of
72
 *      California, Berkeley and its contributors.
73
 * 4. Neither the name of the University nor the names of its contributors
74
 *    may be used to endorse or promote products derived from this software
75
 *    without specific prior written permission.
76
 *
77
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
78
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
80
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
81
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
82
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
83
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
84
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
85
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
86
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
87
 * SUCH DAMAGE.
88
 */
89
90
static const char base32alphabet[] = "0123456789abcdefghijklmnopqrstuv";
91
92
static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p)
93
0
{
94
#ifdef PHP_WIN32
95
  char *opened_path = NULL;
96
  size_t opened_path_len;
97
  wchar_t *cwdw, *random_prefix_w, pathw[MAXPATHLEN];
98
#else
99
0
  char opened_path[MAXPATHLEN];
100
0
  char *trailing_slash;
101
0
#endif
102
0
  uint64_t random;
103
0
  char *random_prefix;
104
0
  char *p;
105
0
  size_t len;
106
0
  char cwd[MAXPATHLEN];
107
0
  cwd_state new_state;
108
0
  int fd = -1;
109
#ifndef HAVE_MKSTEMP
110
  int open_flags = O_CREAT | O_TRUNC | O_RDWR
111
#ifdef PHP_WIN32
112
    | _O_BINARY
113
#endif
114
    ;
115
#endif
116
117
0
  if (!path || !path[0]) {
118
0
    return -1;
119
0
  }
120
121
#ifdef PHP_WIN32
122
  if (!php_win32_check_trailing_space(pfx, strlen(pfx))) {
123
    SetLastError(ERROR_INVALID_NAME);
124
    return -1;
125
  }
126
#endif
127
128
0
  if (!VCWD_GETCWD(cwd, MAXPATHLEN)) {
129
0
    cwd[0] = '\0';
130
0
  }
131
132
0
  new_state.cwd = estrdup(cwd);
133
0
  new_state.cwd_length = strlen(cwd);
134
135
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
136
0
    efree(new_state.cwd);
137
0
    return -1;
138
0
  }
139
140
  /* Extend the prefix to increase randomness */
141
0
  if (php_random_bytes_silent(&random, sizeof(random)) == FAILURE) {
142
0
    random = php_random_generate_fallback_seed();
143
0
  }
144
145
  /* Use a compact encoding to not increase the path len too much, but do not
146
   * mix case to avoid losing randomness on case-insensitive file systems */
147
0
  len = strlen(pfx) + 13 /* log(2**64)/log(strlen(base32alphabet)) */ + 1;
148
0
  random_prefix = emalloc(len);
149
0
  p = zend_mempcpy(random_prefix, pfx, strlen(pfx));
150
0
  while (p + 1 < random_prefix + len) {
151
0
    *p = base32alphabet[random % strlen(base32alphabet)];
152
0
    p++;
153
0
    random /= strlen(base32alphabet);
154
0
  }
155
0
  *p = '\0';
156
157
0
#ifndef PHP_WIN32
158
0
  if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) {
159
0
    trailing_slash = "";
160
0
  } else {
161
0
    trailing_slash = "/";
162
0
  }
163
164
0
  if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, random_prefix) >= MAXPATHLEN) {
165
0
    efree(random_prefix);
166
0
    efree(new_state.cwd);
167
0
    return -1;
168
0
  }
169
0
#endif
170
171
#ifdef PHP_WIN32
172
  cwdw = php_win32_ioutil_any_to_w(new_state.cwd);
173
  random_prefix_w = php_win32_ioutil_any_to_w(random_prefix);
174
  if (!cwdw || !random_prefix_w) {
175
    free(cwdw);
176
    free(random_prefix_w);
177
    efree(random_prefix);
178
    efree(new_state.cwd);
179
    return -1;
180
  }
181
182
  if (GetTempFileNameW(cwdw, random_prefix_w, 0, pathw)) {
183
    opened_path = php_win32_ioutil_conv_w_to_any(pathw, PHP_WIN32_CP_IGNORE_LEN, &opened_path_len);
184
    if (!opened_path || opened_path_len >= MAXPATHLEN) {
185
      free(cwdw);
186
      free(random_prefix_w);
187
      efree(random_prefix);
188
      efree(new_state.cwd);
189
      return -1;
190
    }
191
    assert(strlen(opened_path) == opened_path_len);
192
193
    /* Some versions of windows set the temp file to be read-only,
194
     * which means that opening it will fail... */
195
    if (VCWD_CHMOD(opened_path, 0600)) {
196
      free(cwdw);
197
      free(random_prefix_w);
198
      efree(random_prefix);
199
      efree(new_state.cwd);
200
      free(opened_path);
201
      return -1;
202
    }
203
    fd = VCWD_OPEN_MODE(opened_path, open_flags, 0600);
204
  }
205
206
  free(cwdw);
207
  free(random_prefix_w);
208
#elif defined(HAVE_MKSTEMP)
209
0
  fd = mkstemp(opened_path);
210
#else
211
  if (mktemp(opened_path)) {
212
    fd = VCWD_OPEN(opened_path, open_flags);
213
  }
214
#endif
215
216
#ifdef PHP_WIN32
217
  if (fd != -1 && opened_path_p) {
218
    *opened_path_p = zend_string_init(opened_path, opened_path_len, 0);
219
  }
220
  free(opened_path);
221
#else
222
0
  if (fd != -1 && opened_path_p) {
223
0
    *opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0);
224
0
  }
225
0
#endif
226
0
  efree(new_state.cwd);
227
0
  efree(random_prefix);
228
0
  return fd;
229
0
}
230
/* }}} */
231
232
/*
233
 *  Determine where to place temporary files.
234
 */
235
PHPAPI const char* php_get_temporary_directory(void)
236
0
{
237
  /* Did we determine the temporary directory already? */
238
0
  if (PG(php_sys_temp_dir)) {
239
0
    return PG(php_sys_temp_dir);
240
0
  }
241
242
  /* Is there a temporary directory "sys_temp_dir" in .ini defined? */
243
0
  {
244
0
    char *sys_temp_dir = PG(sys_temp_dir);
245
0
    if (sys_temp_dir) {
246
0
      size_t len = strlen(sys_temp_dir);
247
0
      if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
248
0
        PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
249
0
        return PG(php_sys_temp_dir);
250
0
      } else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
251
0
        PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
252
0
        return PG(php_sys_temp_dir);
253
0
      }
254
0
    }
255
0
  }
256
257
#ifdef PHP_WIN32
258
  /* We can't count on the environment variables TEMP or TMP,
259
   * and so must make the Win32 API call to get the default
260
   * directory for temporary files.  Note this call checks
261
   * the environment values TMP and TEMP (in order) first.
262
   */
263
  {
264
    wchar_t sTemp[MAXPATHLEN];
265
    char *tmp;
266
    size_t len = GetTempPathW(MAXPATHLEN, sTemp);
267
268
    if (!len) {
269
      return NULL;
270
    }
271
272
    if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
273
      return NULL;
274
    }
275
276
    PG(php_sys_temp_dir) = estrndup(tmp, len - 1);
277
278
    free(tmp);
279
    return PG(php_sys_temp_dir);
280
  }
281
#else
282
  /* On Unix use the (usual) TMPDIR environment variable. */
283
0
  {
284
0
    char* s = getenv("TMPDIR");
285
0
    if (s && *s) {
286
0
      size_t len = strlen(s);
287
288
0
      if (s[len - 1] == DEFAULT_SLASH) {
289
0
        PG(php_sys_temp_dir) = estrndup(s, len - 1);
290
0
      } else {
291
0
        PG(php_sys_temp_dir) = estrndup(s, len);
292
0
      }
293
294
0
      return PG(php_sys_temp_dir);
295
0
    }
296
0
  }
297
0
#ifdef P_tmpdir
298
  /* Use the standard default temporary directory. */
299
0
  if (P_tmpdir) {
300
0
    PG(php_sys_temp_dir) = estrdup(P_tmpdir);
301
0
    return PG(php_sys_temp_dir);
302
0
  }
303
0
#endif
304
  /* Shouldn't ever(!) end up here ... last ditch default. */
305
0
  PG(php_sys_temp_dir) = estrdup("/tmp");
306
0
  return PG(php_sys_temp_dir);
307
0
#endif
308
0
}
309
310
/* {{{ php_open_temporary_file
311
 *
312
 * Unlike tempnam(), the supplied dir argument takes precedence
313
 * over the TMPDIR environment variable
314
 * This function should do its best to return a file pointer to a newly created
315
 * unique file, on every platform.
316
 */
317
PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags)
318
0
{
319
0
  int fd;
320
0
  const char *temp_dir;
321
322
0
  if (!pfx) {
323
0
    pfx = "tmp.";
324
0
  }
325
0
  if (opened_path_p) {
326
0
    *opened_path_p = NULL;
327
0
  }
328
329
0
  if (!dir || *dir == '\0') {
330
0
def_tmp:
331
0
    temp_dir = php_get_temporary_directory();
332
333
0
    if (temp_dir &&
334
0
        *temp_dir != '\0' &&
335
0
        (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) {
336
0
      return php_do_open_temporary_file(temp_dir, pfx, opened_path_p);
337
0
    } else {
338
0
      return -1;
339
0
    }
340
0
  }
341
342
0
  if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) {
343
0
    return -1;
344
0
  }
345
346
  /* Try the directory given as parameter. */
347
0
  fd = php_do_open_temporary_file(dir, pfx, opened_path_p);
348
0
  if (fd == -1) {
349
    /* Use default temporary directory. */
350
0
    if (!(flags & PHP_TMP_FILE_SILENT)) {
351
0
      php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory");
352
0
    }
353
0
    goto def_tmp;
354
0
  }
355
0
  return fd;
356
0
}
357
358
PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p)
359
0
{
360
0
  return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT);
361
0
}
362
363
PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p)
364
0
{
365
0
  FILE *fp;
366
0
  int fd = php_open_temporary_fd(dir, pfx, opened_path_p);
367
368
0
  if (fd == -1) {
369
0
    return NULL;
370
0
  }
371
372
0
  fp = fdopen(fd, "r+b");
373
0
  if (fp == NULL) {
374
0
    close(fd);
375
0
  }
376
377
0
  return fp;
378
0
}
379
/* }}} */