mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 01:57:43 +00:00
parser: add support for autobind sockets
af_unix allows for sockets to be bound to a name that is autogenerated. Currently this type of binding is only supported by a very generic rule. unix (bind) type=dgram, but this allows both sockets with specified names and anonymous sockets. Extend unix rule syntax to support specifying just an auto bind socket by specifying addr=auto eg. unix (bind) addr=auto, It is important to note that addr=auto only works for the bind permission as once the socket is bound to an autogenerated address, the addr with have a valid unique value that can be matched against with a regular addr=@name expression Fixes: https://bugs.launchpad.net/apparmor/+bug/1867216 MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/521 Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
parent
c9d01a325d
commit
0a52cf81e3
@ -29,6 +29,9 @@
|
|||||||
#include "profile.h"
|
#include "profile.h"
|
||||||
#include "af_unix.h"
|
#include "af_unix.h"
|
||||||
|
|
||||||
|
/* See unix(7) for autobind address definiation */
|
||||||
|
#define autobind_address_pattern "\\x00[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]";
|
||||||
|
|
||||||
int parse_unix_mode(const char *str_mode, int *mode, int fail)
|
int parse_unix_mode(const char *str_mode, int *mode, int fail)
|
||||||
{
|
{
|
||||||
return parse_X_mode("unix", AA_VALID_NET_PERMS, str_mode, mode, fail);
|
return parse_X_mode("unix", AA_VALID_NET_PERMS, str_mode, mode, fail);
|
||||||
@ -54,7 +57,9 @@ void unix_rule::move_conditionals(struct cond_entry *conds)
|
|||||||
}
|
}
|
||||||
if (strcmp(ent->name, "addr") == 0) {
|
if (strcmp(ent->name, "addr") == 0) {
|
||||||
move_conditional_value("unix socket", &addr, ent);
|
move_conditional_value("unix socket", &addr, ent);
|
||||||
if (addr[0] != '@' && strcmp(addr, "none") != 0)
|
if (addr[0] != '@' &&
|
||||||
|
!(strcmp(addr, "none") == 0 ||
|
||||||
|
strcmp(addr, "auto") == 0))
|
||||||
yyerror("unix rule: invalid value for addr='%s'\n", addr);
|
yyerror("unix rule: invalid value for addr='%s'\n", addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +87,9 @@ void unix_rule::move_peer_conditionals(struct cond_entry *conds)
|
|||||||
}
|
}
|
||||||
if (strcmp(ent->name, "addr") == 0) {
|
if (strcmp(ent->name, "addr") == 0) {
|
||||||
move_conditional_value("unix", &peer_addr, ent);
|
move_conditional_value("unix", &peer_addr, ent);
|
||||||
if (peer_addr[0] != '@' && strcmp(peer_addr, "none") != 0)
|
if ((peer_addr[0] != '@') &&
|
||||||
|
!(strcmp(peer_addr, "none") == 0 ||
|
||||||
|
strcmp(peer_addr, "auto") == 0))
|
||||||
yyerror("unix rule: invalid value for addr='%s'\n", peer_addr);
|
yyerror("unix rule: invalid value for addr='%s'\n", peer_addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,6 +229,12 @@ bool unix_rule::write_addr(std::ostringstream &buffer, const char *addr)
|
|||||||
if (strcmp(addr, "none") == 0) {
|
if (strcmp(addr, "none") == 0) {
|
||||||
/* anonymous */
|
/* anonymous */
|
||||||
buffer << "\\x01";
|
buffer << "\\x01";
|
||||||
|
} else if (strcmp(addr, "auto") == 0) {
|
||||||
|
/* autobind - special autobind rule written already
|
||||||
|
* just generate pattern that matches autobind
|
||||||
|
* generated addresses.
|
||||||
|
*/
|
||||||
|
buffer << autobind_address_pattern;
|
||||||
} else {
|
} else {
|
||||||
/* skip leading @ */
|
/* skip leading @ */
|
||||||
ptype = convert_aaregex_to_pcre(addr + 1, 0, glob_null, buf, &pos);
|
ptype = convert_aaregex_to_pcre(addr + 1, 0, glob_null, buf, &pos);
|
||||||
@ -323,6 +336,33 @@ int unix_rule::gen_policy_re(Profile &prof)
|
|||||||
mask &= ~AA_NET_CREATE;
|
mask &= ~AA_NET_CREATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* write special pattern for autobind? Will not grant bind
|
||||||
|
* on any specific address
|
||||||
|
*/
|
||||||
|
if ((mask & AA_NET_BIND) && (!addr || (strcmp(addr, "auto") == 0))) {
|
||||||
|
std::ostringstream tmp;
|
||||||
|
|
||||||
|
tmp << buffer.str();
|
||||||
|
/* todo: change to out of band separator */
|
||||||
|
/* skip addr, its 0 length */
|
||||||
|
tmp << "\\x00";
|
||||||
|
/* local label option */
|
||||||
|
if (!write_label(tmp, label))
|
||||||
|
goto fail;
|
||||||
|
/* seperator */
|
||||||
|
tmp << "\\x00";
|
||||||
|
|
||||||
|
buf = tmp.str();
|
||||||
|
if (!prof.policy.rules->add_rule(buf.c_str(), deny,
|
||||||
|
map_perms(AA_NET_BIND),
|
||||||
|
map_perms(audit & AA_NET_BIND),
|
||||||
|
dfaflags))
|
||||||
|
goto fail;
|
||||||
|
/* clear if auto, else generic need to generate addr below */
|
||||||
|
if (addr)
|
||||||
|
mask &= ~AA_NET_BIND;
|
||||||
|
}
|
||||||
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
/* local addr */
|
/* local addr */
|
||||||
if (!write_addr(buffer, addr))
|
if (!write_addr(buffer, addr))
|
||||||
|
@ -1263,6 +1263,31 @@ in an abstract socket name. Eg.
|
|||||||
|
|
||||||
unix addr=@*,
|
unix addr=@*,
|
||||||
|
|
||||||
|
Autobound unix domain sockets have a unix sun_path assigned to them
|
||||||
|
by the kernel, as such specifying a policy based address is not possible.
|
||||||
|
The autobinding of sockets can be controlled by specifying the special
|
||||||
|
I<auto> keyword. Eg.
|
||||||
|
|
||||||
|
unix addr=auto,
|
||||||
|
|
||||||
|
To indicate that the rule only applies to auto binding of unix domain
|
||||||
|
sockets. It is important to note this only applies to the I<bind>
|
||||||
|
permission as once the socket is bound to an address it is
|
||||||
|
indistiguishable from a socket that have an addr bound with a
|
||||||
|
specified name. When the I<auto> keyword is used with other permissions
|
||||||
|
or as part of a peer addr it will be replaced with a pattern that
|
||||||
|
can match an autobound socket. Eg. For some kernels
|
||||||
|
|
||||||
|
unix rw addr=auto,
|
||||||
|
|
||||||
|
is transformed to
|
||||||
|
|
||||||
|
unix rw addr=@[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9],
|
||||||
|
|
||||||
|
It is important to note, this pattern may match abstract sockets that
|
||||||
|
were not autobound but have an addr that fits what is generated by
|
||||||
|
the kernel when autobinding a socket.
|
||||||
|
|
||||||
Anonymous unix domain sockets have no sun_path associated with the socket
|
Anonymous unix domain sockets have no sun_path associated with the socket
|
||||||
address, however it can be specified with the special I<none> keyword to
|
address, however it can be specified with the special I<none> keyword to
|
||||||
indicate the rule only applies to anonymous unix domain sockets. Eg.
|
indicate the rule only applies to anonymous unix domain sockets. Eg.
|
||||||
@ -1270,7 +1295,7 @@ indicate the rule only applies to anonymous unix domain sockets. Eg.
|
|||||||
unix addr=none,
|
unix addr=none,
|
||||||
|
|
||||||
If the address component of a rule is not specified then the rule applies
|
If the address component of a rule is not specified then the rule applies
|
||||||
to both abstract and anonymous sockets.
|
to autobind, abstract and anonymous sockets.
|
||||||
|
|
||||||
=head3 Unix socket permissions
|
=head3 Unix socket permissions
|
||||||
|
|
||||||
|
7
parser/tst/simple_tests/unix/bad_attr_5.sd
Normal file
7
parser/tst/simple_tests/unix/bad_attr_5.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix getattr w/peer modifier
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix getattr peer=(addr=auto),
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/bad_opt_5.sd
Normal file
7
parser/tst/simple_tests/unix/bad_opt_5.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix getopt w/peer addr test
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix getopt peer=(addr=auto),
|
||||||
|
}
|
9
parser/tst/simple_tests/unix/bad_peer_2.sd
Normal file
9
parser/tst/simple_tests/unix/bad_peer_2.sd
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
#=Description unix rule with bad 'peer'
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
#
|
||||||
|
|
||||||
|
# path address must be none for anonymous or start with @ for abstract
|
||||||
|
profile foo {
|
||||||
|
unix send peer(addr=auto),
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/bad_shutdown_3.sd
Normal file
7
parser/tst/simple_tests/unix/bad_shutdown_3.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix shutdown w/peer test
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix shutdown peer=(addr=auto),
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/ok_attr_7.sd
Normal file
7
parser/tst/simple_tests/unix/ok_attr_7.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix getattr w/addr acceptance test
|
||||||
|
#=EXRESULT PASS
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix getattr addr=auto,
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/ok_attr_8.sd
Normal file
7
parser/tst/simple_tests/unix/ok_attr_8.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix setattr w/addr acceptance test
|
||||||
|
#=EXRESULT PASS
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix setattr addr=auto,
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/ok_create_4.sd
Normal file
7
parser/tst/simple_tests/unix/ok_create_4.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix create w/addr acceptance test
|
||||||
|
#=EXRESULT PASS
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix create addr=auto,
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/ok_msg_20.sd
Normal file
7
parser/tst/simple_tests/unix/ok_msg_20.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix msg test
|
||||||
|
#=EXRESULT PASS
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix (send) addr=auto,
|
||||||
|
}
|
7
parser/tst/simple_tests/unix/ok_opt_7.sd
Normal file
7
parser/tst/simple_tests/unix/ok_opt_7.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=DESCRIPTION simple unix setopt w/addr acceptance test
|
||||||
|
#=EXRESULT PASS
|
||||||
|
|
||||||
|
profile a_profile {
|
||||||
|
unix setopt addr=auto,
|
||||||
|
}
|
@ -242,6 +242,7 @@ TESTS=aa_exec \
|
|||||||
unix_socket_pathname \
|
unix_socket_pathname \
|
||||||
unix_socket_abstract \
|
unix_socket_abstract \
|
||||||
unix_socket_unnamed \
|
unix_socket_unnamed \
|
||||||
|
unix_socket_autobind \
|
||||||
unlink\
|
unlink\
|
||||||
xattrs\
|
xattrs\
|
||||||
xattrs_profile\
|
xattrs_profile\
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#define MSG_BUF_MAX 1024
|
#define MSG_BUF_MAX 1024
|
||||||
#define PATH_FOR_UNNAMED "none"
|
#define PATH_FOR_UNNAMED "none"
|
||||||
|
#define PATH_FOR_AUTOBIND "auto"
|
||||||
|
|
||||||
static int connection_based_messaging(int sock, int sock_is_peer_sock,
|
static int connection_based_messaging(int sock, int sock_is_peer_sock,
|
||||||
char *msg_buf, size_t msg_buf_len)
|
char *msg_buf, size_t msg_buf_len)
|
||||||
@ -99,7 +100,7 @@ int main (int argc, char *argv[])
|
|||||||
size_t sun_path_len;
|
size_t sun_path_len;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int sock, peer_sock, type, rc;
|
int sock, peer_sock, type, rc;
|
||||||
int unnamed = 0;
|
int unnamed = 0, autobind = 0;
|
||||||
|
|
||||||
if (argc != 5) {
|
if (argc != 5) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@ -124,6 +125,9 @@ int main (int argc, char *argv[])
|
|||||||
addr.sun_path[0] = '\0';
|
addr.sun_path[0] = '\0';
|
||||||
} else if (!strcmp(sun_path, PATH_FOR_UNNAMED)) {
|
} else if (!strcmp(sun_path, PATH_FOR_UNNAMED)) {
|
||||||
unnamed = 1;
|
unnamed = 1;
|
||||||
|
} else if (!strcmp(sun_path, PATH_FOR_AUTOBIND)) {
|
||||||
|
sun_path_len = 0;
|
||||||
|
autobind = 1;
|
||||||
} else {
|
} else {
|
||||||
/* include the nul terminator for pathname addr types */
|
/* include the nul terminator for pathname addr types */
|
||||||
sun_path_len++;
|
sun_path_len++;
|
||||||
@ -195,6 +199,21 @@ int main (int argc, char *argv[])
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autobind) {
|
||||||
|
unsigned int len = sizeof(addr);
|
||||||
|
rc = getsockname(sock, (struct sockaddr *) &addr, &len);
|
||||||
|
if (rc < 0) {
|
||||||
|
perror("FAIL - getsockname");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (len > sizeof(addr)) {
|
||||||
|
perror("FAIL - getsockname: address too long");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
addr.sun_path[0] = '@';
|
||||||
|
sun_path = addr.sun_path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = get_sock_io_timeo(sock);
|
rc = get_sock_io_timeo(sock);
|
||||||
|
@ -18,7 +18,7 @@ message=4a0c83d87aaa7afa2baab5df3ee4df630f0046d5bfb7a3080c550b721f401b3b\
|
|||||||
|
|
||||||
do_test()
|
do_test()
|
||||||
{
|
{
|
||||||
local addr_type="$1" # abstract or unnamed
|
local addr_type="$1" # abstract, auto or unnamed
|
||||||
local test_prog="$2" # server or client
|
local test_prog="$2" # server or client
|
||||||
local l_u_access="$3" # optional local unbound perms
|
local l_u_access="$3" # optional local unbound perms
|
||||||
local l_b_access="$4" # local bound perms
|
local l_b_access="$4" # local bound perms
|
||||||
|
128
tests/regression/apparmor/unix_socket_autobind.sh
Normal file
128
tests/regression/apparmor/unix_socket_autobind.sh
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 Canonical, Ltd.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of version 2 of the GNU General Public
|
||||||
|
# License published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, contact Canonical Ltd.
|
||||||
|
|
||||||
|
#=NAME unix_socket_autobind abstract sockets
|
||||||
|
#=DESCRIPTION
|
||||||
|
# This tests access to autobinding abstract unix domain sockets. The
|
||||||
|
# server opens a socket, forks a client with it's own profile, passes
|
||||||
|
# an fd across exec, sends a message to the client over the socket, and
|
||||||
|
# sees what happens.
|
||||||
|
#=END
|
||||||
|
#
|
||||||
|
# TODO: peer_addr auto, just generates a pattern it would be better if we
|
||||||
|
# could extract the bound socket name and pass that in to the profile
|
||||||
|
# generation
|
||||||
|
|
||||||
|
pwd=`dirname $0`
|
||||||
|
pwd=`cd $pwd ; /bin/pwd`
|
||||||
|
|
||||||
|
bin=$pwd
|
||||||
|
|
||||||
|
. $bin/prologue.inc
|
||||||
|
. $bin/unix_socket.inc
|
||||||
|
requires_kernel_features policy/versions/v7
|
||||||
|
requires_kernel_features network/af_unix
|
||||||
|
requires_parser_support "unix,"
|
||||||
|
|
||||||
|
settest unix_socket
|
||||||
|
|
||||||
|
addr=auto
|
||||||
|
#TODO: replace client_addr pattern with actual autobound address
|
||||||
|
client_addr=@[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].client
|
||||||
|
|
||||||
|
# Test autobind stream server and client
|
||||||
|
do_test "autobind" \
|
||||||
|
"server" \
|
||||||
|
"create,setopt" \
|
||||||
|
"bind,listen,getopt,shutdown,getattr" \
|
||||||
|
stream \
|
||||||
|
"$addr" \
|
||||||
|
"accept,read,write" \
|
||||||
|
"unconfined" \
|
||||||
|
"" \
|
||||||
|
dgram \
|
||||||
|
"@autoXXX" \
|
||||||
|
"${test}XXX" \
|
||||||
|
""
|
||||||
|
do_test "autobind" \
|
||||||
|
"client" \
|
||||||
|
"" \
|
||||||
|
"create,getopt,setopt,getattr" \
|
||||||
|
stream \
|
||||||
|
"" \
|
||||||
|
"connect,write,read" \
|
||||||
|
"$test" \
|
||||||
|
"$addr" \
|
||||||
|
seqpacket \
|
||||||
|
"" \
|
||||||
|
"${test}XXX" \
|
||||||
|
"@autoXXX"
|
||||||
|
|
||||||
|
# Test autobind dgram server and client
|
||||||
|
do_test "autobind" \
|
||||||
|
"server" \
|
||||||
|
"create,setopt" \
|
||||||
|
"bind,getopt,shutdown,getattr" \
|
||||||
|
dgram \
|
||||||
|
"$addr" \
|
||||||
|
"read,write" \
|
||||||
|
"unconfined" \
|
||||||
|
"$client_addr" \
|
||||||
|
seqpacket \
|
||||||
|
"@autoXXX" \
|
||||||
|
"${test}XXX" \
|
||||||
|
"${client_addr}XXX"
|
||||||
|
do_test "autobind" \
|
||||||
|
"client" \
|
||||||
|
"create,setopt,getattr" \
|
||||||
|
"bind,getopt,getattr" \
|
||||||
|
dgram \
|
||||||
|
"$client_addr" \
|
||||||
|
"write,read" \
|
||||||
|
"$test" \
|
||||||
|
"$addr" \
|
||||||
|
stream \
|
||||||
|
"${client_addr}XXX" \
|
||||||
|
"${test}XXX" \
|
||||||
|
"@autoXXX"
|
||||||
|
|
||||||
|
# Test autobind seqpacket server and client
|
||||||
|
do_test "autobind" \
|
||||||
|
"server" \
|
||||||
|
"create,setopt" \
|
||||||
|
"bind,listen,getopt,shutdown,getattr" \
|
||||||
|
seqpacket \
|
||||||
|
"$addr" \
|
||||||
|
"accept,read,write" \
|
||||||
|
"unconfined" \
|
||||||
|
"" \
|
||||||
|
stream \
|
||||||
|
"@autoXXX" \
|
||||||
|
"${test}XXX" \
|
||||||
|
""
|
||||||
|
do_test "autobind" \
|
||||||
|
"client" \
|
||||||
|
"" \
|
||||||
|
"create,getopt,setopt,getattr" \
|
||||||
|
seqpacket \
|
||||||
|
"" \
|
||||||
|
"connect,write,read" \
|
||||||
|
"$test" \
|
||||||
|
"$addr" \
|
||||||
|
dgram \
|
||||||
|
"" \
|
||||||
|
"${test}XXX" \
|
||||||
|
"@autoXXX"
|
@ -44,7 +44,9 @@ static int connection_based_messaging(int sock, struct sockaddr_un *peer_addr,
|
|||||||
if (peer_addr) {
|
if (peer_addr) {
|
||||||
rc = connect(sock, (struct sockaddr *)peer_addr, peer_addr_len);
|
rc = connect(sock, (struct sockaddr *)peer_addr, peer_addr_len);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
perror("FAIL CLIENT - connect");
|
if (peer_addr_len > 0 && peer_addr->sun_path[0] == 0)
|
||||||
|
peer_addr->sun_path[0] = '@';
|
||||||
|
fprintf(stderr, "FAIL CLIENT - connect '%s'(%d): %m", peer_addr->sun_path, peer_addr_len);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,10 @@ exception_not_raised = [
|
|||||||
'unix/bad_regex_04.sd',
|
'unix/bad_regex_04.sd',
|
||||||
'unix/bad_shutdown_1.sd',
|
'unix/bad_shutdown_1.sd',
|
||||||
'unix/bad_shutdown_2.sd',
|
'unix/bad_shutdown_2.sd',
|
||||||
|
'unix/bad_peer_2.sd',
|
||||||
|
'unix/bad_attr_5.sd',
|
||||||
|
'unix/bad_opt_5.sd',
|
||||||
|
'unix/bad_shutdown_3.sd',
|
||||||
'vars/boolean/boolean_bad_2.sd',
|
'vars/boolean/boolean_bad_2.sd',
|
||||||
'vars/boolean/boolean_bad_3.sd',
|
'vars/boolean/boolean_bad_3.sd',
|
||||||
'vars/boolean/boolean_bad_4.sd',
|
'vars/boolean/boolean_bad_4.sd',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user