Skip to content

Commit 9eb2284

Browse files
authored
ext/sockets: Additional refactorings to socket_set_option() (#17186)
* ext/socket: Reduce scope of variables * ext/socket: Throw TypeErrors when not passing a string for options requiring one * ext/sockets: Throw ValueError when string has null bytes for options not passing string length * ext/sockets: Throw ValueError when string is too long And replace calls to strlcpy to memcpy
1 parent b27bbd3 commit 9eb2284

7 files changed

+243
-27
lines changed

ext/sockets/sockets.c

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,8 +1888,6 @@ PHP_FUNCTION(socket_set_option)
18881888
zend_long level, optname;
18891889
void *opt_ptr;
18901890
HashTable *opt_ht;
1891-
zval *l_onoff, *l_linger;
1892-
zval *sec, *usec;
18931891

18941892
ZEND_PARSE_PARAMETERS_START(4, 4)
18951893
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
@@ -1930,13 +1928,12 @@ PHP_FUNCTION(socket_set_option)
19301928
switch (optname) {
19311929
#ifdef TCP_CONGESTION
19321930
case TCP_CONGESTION: {
1933-
if (Z_TYPE_P(arg4) == IS_STRING) {
1934-
opt_ptr = Z_STRVAL_P(arg4);
1935-
optlen = Z_STRLEN_P(arg4);
1936-
} else {
1937-
opt_ptr = "";
1938-
optlen = 0;
1931+
if (Z_TYPE_P(arg4) != IS_STRING) {
1932+
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is TCP_CONGESTION, %s given", zend_zval_value_name(arg4));
1933+
RETURN_THROWS();
19391934
}
1935+
opt_ptr = Z_STRVAL_P(arg4);
1936+
optlen = Z_STRLEN_P(arg4);
19401937
if (setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen) != 0) {
19411938
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
19421939
RETURN_FALSE;
@@ -1949,11 +1946,23 @@ PHP_FUNCTION(socket_set_option)
19491946
#ifdef TCP_FUNCTION_BLK
19501947
case TCP_FUNCTION_BLK: {
19511948
if (Z_TYPE_P(arg4) != IS_STRING) {
1952-
php_error_docref(NULL, E_WARNING, "Invalid tcp stack name argument type");
1953-
RETURN_FALSE;
1949+
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is TCP_FUNCTION_BLK, %s given", zend_zval_value_name(arg4));
1950+
RETURN_THROWS();
1951+
}
1952+
if (zend_str_has_nul_byte(Z_STR_P(arg4))) {
1953+
zend_argument_value_error(4, "must not contain null bytes when argument #3 ($option) is TCP_FUNCTION_BLK");
1954+
RETURN_THROWS();
1955+
}
1956+
/* [...] the unique name of the TCP stack, and should be no longer than
1957+
* TCP_FUNCTION_NAME_LEN_MAX-1 characters in length.
1958+
* https://github.com/freebsd/freebsd-src/blob/da2c88dfcf4f425e6e0a58d6df3a7c8e88d8df92/share/man/man9/tcp_functions.9#L193C1-L194C50 */
1959+
if (Z_STRLEN_P(arg4) >= TCP_FUNCTION_NAME_LEN_MAX) {
1960+
zend_argument_value_error(4, "must be less than %zu bytes when argument #3 ($option) is TCP_FUNCTION_BLK", TCP_FUNCTION_NAME_LEN_MAX);
1961+
RETURN_THROWS();
19541962
}
19551963
struct tcp_function_set tfs = {0};
1956-
strlcpy(tfs.function_set_name, Z_STRVAL_P(arg4), TCP_FUNCTION_NAME_LEN_MAX);
1964+
/* Copy the trailing nul byte */
1965+
memcpy(tfs.function_set_name, Z_STRVAL_P(arg4), Z_STRLEN_P(arg4)+1);
19571966

19581967
optlen = sizeof(tfs);
19591968
opt_ptr = &tfs;
@@ -1976,6 +1985,7 @@ PHP_FUNCTION(socket_set_option)
19761985
case SO_LINGER: {
19771986
const char l_onoff_key[] = "l_onoff";
19781987
const char l_linger_key[] = "l_linger";
1988+
zval *l_onoff, *l_linger;
19791989

19801990
if (Z_TYPE_P(arg4) != IS_ARRAY) {
19811991
if (UNEXPECTED(Z_TYPE_P(arg4) != IS_OBJECT)) {
@@ -2022,6 +2032,7 @@ PHP_FUNCTION(socket_set_option)
20222032
case SO_SNDTIMEO: {
20232033
const char sec_key[] = "sec";
20242034
const char usec_key[] = "usec";
2035+
zval *sec, *usec;
20252036

20262037
if (Z_TYPE_P(arg4) != IS_ARRAY) {
20272038
if (UNEXPECTED(Z_TYPE_P(arg4) != IS_OBJECT)) {
@@ -2078,25 +2089,33 @@ PHP_FUNCTION(socket_set_option)
20782089
}
20792090
#ifdef SO_BINDTODEVICE
20802091
case SO_BINDTODEVICE: {
2081-
if (Z_TYPE_P(arg4) == IS_STRING) {
2082-
opt_ptr = Z_STRVAL_P(arg4);
2083-
optlen = Z_STRLEN_P(arg4);
2084-
} else {
2085-
opt_ptr = "";
2086-
optlen = 0;
2092+
if (Z_TYPE_P(arg4) != IS_STRING) {
2093+
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is SO_BINDTODEVICE, %s given", zend_zval_value_name(arg4));
2094+
RETURN_THROWS();
20872095
}
2096+
opt_ptr = Z_STRVAL_P(arg4);
2097+
optlen = Z_STRLEN_P(arg4);
20882098
break;
20892099
}
20902100
#endif
20912101

20922102
#ifdef SO_ACCEPTFILTER
20932103
case SO_ACCEPTFILTER: {
20942104
if (Z_TYPE_P(arg4) != IS_STRING) {
2095-
php_error_docref(NULL, E_WARNING, "Invalid filter argument type");
2096-
RETURN_FALSE;
2105+
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is SO_ACCEPTFILTER, %s given", zend_zval_value_name(arg4));
2106+
RETURN_THROWS();
2107+
}
2108+
if (zend_str_has_nul_byte(Z_STR_P(arg4))) {
2109+
zend_argument_value_error(4, "must not contain null bytes when argument #3 ($option) is SO_ACCEPTFILTER");
2110+
RETURN_THROWS();
20972111
}
20982112
struct accept_filter_arg af = {0};
2099-
strlcpy(af.af_name, Z_STRVAL_P(arg4), sizeof(af.af_name));
2113+
if (Z_STRLEN_P(arg4) >= sizeof(af.af_name)) {
2114+
zend_argument_value_error(4, "must be less than %zu bytes when argument #3 ($option) is SO_ACCEPTFILTER", sizeof(af.af_name));
2115+
RETURN_THROWS();
2116+
}
2117+
/* Copy the trailing nul byte */
2118+
memcpy(af.af_name, Z_STRVAL_P(arg4), Z_STRLEN_P(arg4)+1);
21002119
opt_ptr = ⁡
21012120
optlen = sizeof(af);
21022121
break;
@@ -2111,8 +2130,8 @@ PHP_FUNCTION(socket_set_option)
21112130
RETURN_FALSE;
21122131
}
21132132
if (Z_TYPE_P(arg4) != IS_STRING) {
2114-
php_error_docref(NULL, E_WARNING, "Invalid filter argument type");
2115-
RETURN_FALSE;
2133+
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is FIL_ATTACH or FIL_DETACH, %s given", zend_zval_value_name(arg4));
2134+
RETURN_THROWS();
21162135
}
21172136
opt_ptr = Z_STRVAL_P(arg4);
21182137
optlen = Z_STRLEN_P(arg4);

ext/sockets/tests/socket_set_option_acf.phpt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@ if (!$socket) {
2121
}
2222
try {
2323
var_dump(socket_set_option( $socket, SOL_SOCKET, SO_ACCEPTFILTER, 1));
24-
} catch (\ValueError $e) {
25-
echo $e->getMessage() . \PHP_EOL;
24+
} catch (\Throwable $e) {
25+
echo $e::class, ': ', $e->getMessage(), \PHP_EOL;
2626
}
2727
socket_listen($socket);
2828
var_dump(socket_set_option( $socket, SOL_SOCKET, SO_ACCEPTFILTER, "httpready"));
2929
var_dump(socket_get_option( $socket, SOL_SOCKET, SO_ACCEPTFILTER));
3030
socket_close($socket);
3131
?>
32-
--EXPECTF--
33-
Warning: socket_set_option(): Invalid filter argument type in %s on line %d
34-
bool(false)
32+
--EXPECT--
33+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is SO_ACCEPTFILTER, int given
3534
bool(true)
3635
array(1) {
3736
["af_name"]=>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
socket_set_option($socket, SOL_SOCKET, SO_BINDTODEVICE, INVALID_TYPE_FOR_OPTION)
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
8+
if (!defined('SOL_FILTER')) {
9+
die('skip SOL_FILTER not available.');
10+
}
11+
if (!defined('FIL_ATTACH')) {
12+
die('skip FIL_ATTACH not available.');
13+
}
14+
if (!defined('FIL_DETACH')) {
15+
die('skip FIL_DETACH not available.');
16+
}
17+
18+
?>
19+
--FILE--
20+
<?php
21+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
22+
if (!$socket) {
23+
die('Unable to create AF_INET socket [socket]');
24+
}
25+
26+
// TODO Warning when using FIL_ATTACH/FIL_DETACH option when level is not SOL_FILTER
27+
try {
28+
$ret = socket_set_option($socket, SOL_FILTER, FIL_ATTACH, new stdClass());
29+
var_dump($ret);
30+
} catch (Throwable $e) {
31+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
32+
}
33+
try {
34+
$ret = socket_set_option($socket, SOL_FILTER, FIL_DETACH, new stdClass());
35+
var_dump($ret);
36+
} catch (Throwable $e) {
37+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
38+
}
39+
40+
socket_close($socket);
41+
?>
42+
--EXPECT--
43+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is FIL_ATTACH or FIL_DETACH, stdClass given
44+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is FIL_ATTACH or FIL_DETACH, stdClass given
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
socket_set_option($socket, SOL_SOCKET, SO_ACCEPTFILTER, INVALID_TYPE_FOR_OPTION)
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
8+
if (!defined('SOL_SOCKET')) {
9+
die('skip SOL_SOCKET not available.');
10+
}
11+
if (!defined('SO_ACCEPTFILTER')) {
12+
die('skip SO_ACCEPTFILTER not available.');
13+
}
14+
15+
?>
16+
--FILE--
17+
<?php
18+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
19+
if (!$socket) {
20+
die('Unable to create AF_INET socket [socket]');
21+
}
22+
23+
try {
24+
$ret = socket_set_option($socket, SOL_SOCKET, SO_ACCEPTFILTER, new stdClass());
25+
var_dump($ret);
26+
} catch (Throwable $e) {
27+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
28+
}
29+
try {
30+
$ret = socket_set_option($socket, SOL_SOCKET, SO_ACCEPTFILTER, "string\0with\0null\0bytes");
31+
var_dump($ret);
32+
} catch (Throwable $e) {
33+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
34+
}
35+
try {
36+
$ret = socket_set_option($socket, SOL_SOCKET, SO_ACCEPTFILTER, str_repeat("a", 2048));
37+
var_dump($ret);
38+
} catch (Throwable $e) {
39+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
40+
}
41+
42+
socket_close($socket);
43+
?>
44+
--EXPECTF--
45+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is SO_ACCEPTFILTER, stdClass given
46+
ValueError: socket_set_option(): Argument #4 ($value) must not contain null bytes when argument #3 ($option) is SO_ACCEPTFILTER
47+
ValueError: socket_set_option(): Argument #4 ($value) must be less than %d bytes when argument #3 ($option) is SO_ACCEPTFILTER
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
socket_set_option($socket, SOL_SOCKET, SO_BINDTODEVICE, INVALID_TYPE_FOR_OPTION)
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
8+
if (!defined('SOL_SOCKET')) {
9+
die('skip SOL_SOCKET not available.');
10+
}
11+
if (!defined('SO_BINDTODEVICE')) {
12+
die('skip SO_BINDTODEVICE not available.');
13+
}
14+
15+
?>
16+
--FILE--
17+
<?php
18+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
19+
if (!$socket) {
20+
die('Unable to create AF_INET socket [socket]');
21+
}
22+
23+
try {
24+
$ret = socket_set_option($socket, SOL_SOCKET, SO_BINDTODEVICE, new stdClass());
25+
var_dump($ret);
26+
} catch (Throwable $e) {
27+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
28+
}
29+
30+
socket_close($socket);
31+
?>
32+
--EXPECT--
33+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is SO_BINDTODEVICE, stdClass given
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
socket_set_option($socket, SOL_TCP, TCP_CONGESTION, INVALID_TYPE_FOR_OPTION)
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
8+
if (!defined('TCP_CONGESTION')) {
9+
die('skip TCP_CONGESTION not available.');
10+
}
11+
12+
?>
13+
--FILE--
14+
<?php
15+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
16+
if (!$socket) {
17+
die('Unable to create AF_INET socket [socket]');
18+
}
19+
20+
try {
21+
$ret = socket_set_option($socket, SOL_TCP, TCP_CONGESTION, new stdClass());
22+
var_dump($ret);
23+
} catch (Throwable $e) {
24+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
25+
}
26+
27+
socket_close($socket);
28+
?>
29+
--EXPECT--
30+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is TCP_CONGESTION, stdClass given
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
socket_set_option($socket, SOL_TCP, TCP_FUNCTION_BLK, INVALID_TYPE_FOR_OPTION)
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
8+
if (!defined('TCP_FUNCTION_BLK')) {
9+
die('skip TCP_FUNCTION_BLK not available.');
10+
}
11+
12+
?>
13+
--FILE--
14+
<?php
15+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
16+
if (!$socket) {
17+
die('Unable to create AF_INET socket [socket]');
18+
}
19+
20+
try {
21+
$ret = socket_set_option($socket, SOL_TCP, TCP_FUNCTION_BLK, new stdClass());
22+
var_dump($ret);
23+
} catch (Throwable $e) {
24+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
25+
}
26+
try {
27+
$ret = socket_set_option($socket, SOL_TCP, TCP_FUNCTION_BLK, "string\0with\0null\0bytes");
28+
var_dump($ret);
29+
} catch (Throwable $e) {
30+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
31+
}
32+
try {
33+
$ret = socket_set_option($socket, SOL_TCP, TCP_FUNCTION_BLK, str_repeat("a", 2048));
34+
var_dump($ret);
35+
} catch (Throwable $e) {
36+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
37+
}
38+
39+
socket_close($socket);
40+
?>
41+
--EXPECTF--
42+
TypeError: socket_set_option(): Argument #4 ($value) must be of type string when argument #3 ($option) is TCP_FUNCTION_BLK, stdClass given
43+
ValueError: socket_set_option(): Argument #4 ($value) must not contain null bytes when argument #3 ($option) is TCP_FUNCTION_BLK
44+
ValueError: socket_set_option(): Argument #4 ($value) must be less than %d bytes when argument #3 ($option) is TCP_FUNCTION_BLK

0 commit comments

Comments
 (0)