Coverage Report

Created: 2025-09-27 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/php_open_temporary_file.c
Line
Count
Source
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
      free(opened_path);
190
      return -1;
191
    }
192
    assert(strlen(opened_path) == opened_path_len);
193
194
    /* Some versions of windows set the temp file to be read-only,
195
     * which means that opening it will fail... */
196
    if (VCWD_CHMOD(opened_path, 0600)) {
197
      free(cwdw);
198
      free(random_prefix_w);
199
      efree(random_prefix);
200
      efree(new_state.cwd);
201
      free(opened_path);
202
      return -1;
203
    }
204
    fd = VCWD_OPEN_MODE(opened_path, open_flags, 0600);
205
  }
206
207
  free(cwdw);
208
  free(random_prefix_w);
209
#elif defined(HAVE_MKSTEMP)
210
0
  fd = mkstemp(opened_path);
211
#else
212
  if (mktemp(opened_path)) {
213
    fd = VCWD_OPEN(opened_path, open_flags);
214
  }
215
#endif
216
217
#ifdef PHP_WIN32
218
  if (fd != -1 && opened_path_p) {
219
    *opened_path_p = zend_string_init(opened_path, opened_path_len, 0);
220
  }
221
  free(opened_path);
222
#else
223
0
  if (fd != -1 && opened_path_p) {
224
0
    *opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0);
225
0
  }
226
0
#endif
227
0
  efree(new_state.cwd);
228
0
  efree(random_prefix);
229
0
  return fd;
230
0
}
231
/* }}} */
232
233
/*
234
 *  Determine where to place temporary files.
235
 */
236
PHPAPI const char* php_get_temporary_directory(void)
237
0
{
238
  /* Did we determine the temporary directory already? */
239
0
  if (PG(php_sys_temp_dir)) {
240
0
    return PG(php_sys_temp_dir);
241
0
  }
242
243
  /* Is there a temporary directory "sys_temp_dir" in .ini defined? */
244
0
  {
245
0
    char *sys_temp_dir = PG(sys_temp_dir);
246
0
    if (sys_temp_dir) {
247
0
      size_t len = strlen(sys_temp_dir);
248
0
      if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
249
0
        PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
250
0
        return PG(php_sys_temp_dir);
251
0
      } else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
252
0
        PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
253
0
        return PG(php_sys_temp_dir);
254
0
      }
255
0
    }
256
0
  }
257
258
#ifdef PHP_WIN32
259
  /* We can't count on the environment variables TEMP or TMP,
260
   * and so must make the Win32 API call to get the default
261
   * directory for temporary files.  Note this call checks
262
   * the environment values TMP and TEMP (in order) first.
263
   */
264
  {
265
    wchar_t sTemp[MAXPATHLEN];
266
    char *tmp;
267
    size_t len = GetTempPathW(MAXPATHLEN, sTemp);
268
269
    if (!len) {
270
      return NULL;
271
    }
272
273
    if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
274
      return NULL;
275
    }
276
277
    PG(php_sys_temp_dir) = estrndup(tmp, len - 1);
278
279
    free(tmp);
280
    return PG(php_sys_temp_dir);
281
  }
282
#else
283
  /* On Unix use the (usual) TMPDIR environment variable. */
284
0
  {
285
0
    char* s = getenv("TMPDIR");
286
0
    if (s && *s) {
287
0
      size_t len = strlen(s);
288
289
0
      if (s[len - 1] == DEFAULT_SLASH) {
290
0
        PG(php_sys_temp_dir) = estrndup(s, len - 1);
291
0
      } else {
292
0
        PG(php_sys_temp_dir) = estrndup(s, len);
293
0
      }
294
295
0
      return PG(php_sys_temp_dir);
296
0
    }
297
0
  }
298
0
#ifdef P_tmpdir
299
  /* Use the standard default temporary directory. */
300
0
  if (P_tmpdir) {
301
0
    PG(php_sys_temp_dir) = estrdup(P_tmpdir);
302
0
    return PG(php_sys_temp_dir);
303
0
  }
304
0
#endif
305
  /* Shouldn't ever(!) end up here ... last ditch default. */
306
0
  PG(php_sys_temp_dir) = estrdup("/tmp");
307
0
  return PG(php_sys_temp_dir);
308
0
#endif
309
0
}
310
311
/* {{{ php_open_temporary_file
312
 *
313
 * Unlike tempnam(), the supplied dir argument takes precedence
314
 * over the TMPDIR environment variable
315
 * This function should do its best to return a file pointer to a newly created
316
 * unique file, on every platform.
317
 */
318
PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags)
319
0
{
320
0
  int fd;
321
0
  const char *temp_dir;
322
323
0
  if (!pfx) {
324
0
    pfx = "tmp.";
325
0
  }
326
0
  if (opened_path_p) {
327
0
    *opened_path_p = NULL;
328
0
  }
329
330
0
  if (!dir || *dir == '\0') {
331
0
def_tmp:
332
0
    temp_dir = php_get_temporary_directory();
333
334
0
    if (temp_dir &&
335
0
        *temp_dir != '\0' &&
336
0
        (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) {
337
0
      return php_do_open_temporary_file(temp_dir, pfx, opened_path_p);
338
0
    } else {
339
0
      return -1;
340
0
    }
341
0
  }
342
343
0
  if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) {
344
0
    return -1;
345
0
  }
346
347
  /* Try the directory given as parameter. */
348
0
  fd = php_do_open_temporary_file(dir, pfx, opened_path_p);
349
0
  if (fd == -1) {
350
    /* Use default temporary directory. */
351
0
    if (!(flags & PHP_TMP_FILE_SILENT)) {
352
0
      php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory");
353
0
    }
354
0
    goto def_tmp;
355
0
  }
356
0
  return fd;
357
0
}
358
359
PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p)
360
0
{
361
0
  return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT);
362
0
}
363
364
PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p)
365
0
{
366
0
  FILE *fp;
367
0
  int fd = php_open_temporary_fd(dir, pfx, opened_path_p);
368
369
0
  if (fd == -1) {
370
0
    return NULL;
371
0
  }
372
373
0
  fp = fdopen(fd, "r+b");
374
0
  if (fp == NULL) {
375
0
    close(fd);
376
0
  }
377
378
0
  return fp;
379
0
}
380
/* }}} */