Skip to content

Commit b4325d6

Browse files
committed
Improve randomness of uploaded file names and files created by tempnam()
Closes phpGH-14364
1 parent 7130a17 commit b4325d6

File tree

4 files changed

+55
-20
lines changed

4 files changed

+55
-20
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ PHP NEWS
2424
. Fixed GH-13581 no space available for TLS on NetBSD. (Paul Ripke)
2525
. Added fiber Sys-V loongarch64 support. (qiangxuhui)
2626
. Adjusted closure names to include the parent function's name. (timwolla)
27+
. Improve randomness of uploaded file names and files created by tempnam().
28+
(Arnaud)
2729

2830
- Curl:
2931
. Deprecated the CURLOPT_BINARYTRANSFER constant. (divinity76)

UPGRADING

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ PHP 8.4 UPGRADE NOTES
2929

3030
- Core:
3131
. The type of PHP_DEBUG and PHP_ZTS constants changed to bool.
32+
. The name of uploaded files and files created by the tempnam() function are
33+
now 13 bytes longer. Total length is platform-dependent.
3234

3335
- DOM:
3436
. Added DOMNode::compareDocumentPosition() and DOMNode::DOCUMENT_POSITION_*
@@ -237,7 +239,7 @@ PHP 8.4 UPGRADE NOTES
237239
ed25519, x448 and ed448 fields are supported in openssl_pkey_new and
238240
openssl_pkey_get_details as well as openssl_sign and openssl_verify were
239241
extended to support those keys.
240-
242+
241243

242244
- Phar:
243245
. Added support for the unix timestamp extension for zip archives.

ext/standard/tests/file/tempnam_variation9.phpt

+10-10
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,17 @@ if (file_exists($file_path)) {
5757
--EXPECTF--
5858
*** Testing tempnam() maximum prefix size ***
5959
-- Iteration 0 --
60-
File name is => begin_%rx{7}%r_end%r.{6}%r
61-
File name length is => 23
60+
File name is => begin_%rx{7}%r_end%r.{19}%r
61+
File name length is => 36
6262
-- Iteration 1 --
63-
File name is => begin_%rx{53}%r_end%r.{6}%r
64-
File name length is => 69
63+
File name is => begin_%rx{53}%r_end%r.{19}%r
64+
File name length is => 82
6565
-- Iteration 2 --
66-
File name is => begin_%rx{54}%r_en%r.{6}%r
67-
File name length is => 69
66+
File name is => begin_%rx{54}%r_en%r.{19}%r
67+
File name length is => 82
6868
-- Iteration 3 --
69-
File name is => begin_%rx{55}%r_e%r.{6}%r
70-
File name length is => 69
69+
File name is => begin_%rx{55}%r_e%r.{19}%r
70+
File name length is => 82
7171
-- Iteration 4 --
72-
File name is => begin_%rx{57}%r%r.{6}%r
73-
File name length is => 69
72+
File name is => begin_%rx{57}%r%r.{19}%r
73+
File name length is => 82

main/php_open_temporary_file.c

+40-9
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
*/
1616

1717
#include "php.h"
18+
#include "zend_long.h"
1819
#include "php_open_temporary_file.h"
20+
#include "ext/random/php_random.h"
21+
#include "zend_operators.h"
1922

2023
#include <errno.h>
2124
#include <sys/types.h>
@@ -84,16 +87,22 @@
8487
* SUCH DAMAGE.
8588
*/
8689

90+
static const char base32alphabet[] = "0123456789abcdefghijklmnopqrstuv";
91+
8792
static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p)
8893
{
8994
#ifdef PHP_WIN32
9095
char *opened_path = NULL;
9196
size_t opened_path_len;
92-
wchar_t *cwdw, *pfxw, pathw[MAXPATHLEN];
97+
wchar_t *cwdw, *random_prefix_w, pathw[MAXPATHLEN];
9398
#else
9499
char opened_path[MAXPATHLEN];
95100
char *trailing_slash;
96101
#endif
102+
uint64_t random;
103+
char *random_prefix;
104+
char *p;
105+
size_t len;
97106
char cwd[MAXPATHLEN];
98107
cwd_state new_state;
99108
int fd = -1;
@@ -128,34 +137,54 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st
128137
return -1;
129138
}
130139

140+
/* Extend the prefix to increase randomness */
141+
if (php_random_bytes_silent(&random, sizeof(random)) == FAILURE) {
142+
random = php_random_generate_fallback_seed();
143+
}
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+
len = strlen(pfx) + 13 /* log(2**64)/log(strlen(base32alphabet)) */ + 1;
148+
random_prefix = emalloc(len);
149+
p = zend_mempcpy(random_prefix, pfx, strlen(pfx));
150+
while (p + 1 < random_prefix + len) {
151+
*p = base32alphabet[random % strlen(base32alphabet)];
152+
p++;
153+
random /= strlen(base32alphabet);
154+
}
155+
*p = '\0';
156+
131157
#ifndef PHP_WIN32
132158
if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) {
133159
trailing_slash = "";
134160
} else {
135161
trailing_slash = "/";
136162
}
137163

138-
if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, pfx) >= MAXPATHLEN) {
164+
if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, random_prefix) >= MAXPATHLEN) {
165+
efree(random_prefix);
139166
efree(new_state.cwd);
140167
return -1;
141168
}
142169
#endif
143170

144171
#ifdef PHP_WIN32
145172
cwdw = php_win32_ioutil_any_to_w(new_state.cwd);
146-
pfxw = php_win32_ioutil_any_to_w(pfx);
147-
if (!cwdw || !pfxw) {
173+
random_prefix_w = php_win32_ioutil_any_to_w(random_prefix);
174+
if (!cwdw || !random_prefix_w) {
148175
free(cwdw);
149-
free(pfxw);
176+
free(random_prefix_w);
177+
efree(random_prefix);
150178
efree(new_state.cwd);
151179
return -1;
152180
}
153181

154-
if (GetTempFileNameW(cwdw, pfxw, 0, pathw)) {
182+
if (GetTempFileNameW(cwdw, random_prefix_w, 0, pathw)) {
155183
opened_path = php_win32_ioutil_conv_w_to_any(pathw, PHP_WIN32_CP_IGNORE_LEN, &opened_path_len);
156184
if (!opened_path || opened_path_len >= MAXPATHLEN) {
157185
free(cwdw);
158-
free(pfxw);
186+
free(random_prefix_w);
187+
efree(random_prefix);
159188
efree(new_state.cwd);
160189
return -1;
161190
}
@@ -165,7 +194,8 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st
165194
* which means that opening it will fail... */
166195
if (VCWD_CHMOD(opened_path, 0600)) {
167196
free(cwdw);
168-
free(pfxw);
197+
free(random_prefix_w);
198+
efree(random_prefix);
169199
efree(new_state.cwd);
170200
free(opened_path);
171201
return -1;
@@ -174,7 +204,7 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st
174204
}
175205

176206
free(cwdw);
177-
free(pfxw);
207+
free(random_prefix_w);
178208
#elif defined(HAVE_MKSTEMP)
179209
fd = mkstemp(opened_path);
180210
#else
@@ -194,6 +224,7 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st
194224
}
195225
#endif
196226
efree(new_state.cwd);
227+
efree(random_prefix);
197228
return fd;
198229
}
199230
/* }}} */

0 commit comments

Comments
 (0)