Coverage Report

Created: 2026-06-13 07:01

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