[PATCH v10 09/13] landlock: Add network rules and TCP hooks support
Mickaël Salaün
mic at digikod.net
Wed Apr 26 14:15:28 UTC 2023
On 21/04/2023 11:39, Konstantin Meskhidze (A) wrote:
>
>
> 4/16/2023 7:11 PM, Mickaël Salaün пишет:
>>
>> On 23/03/2023 09:52, Konstantin Meskhidze wrote:
>>> This commit adds network rules support in the ruleset management
>>> helpers and the landlock_create_ruleset syscall.
>>> Refactor user space API to support network actions. Add new network
>>> access flags, network rule and network attributes. Increment Landlock
>>> ABI version. Expand access_masks_t to u32 to be sure network access
>>> rights can be stored. Implement socket_bind() and socket_connect()
>>> LSM hooks, which enable to restrict TCP socket binding and connection
>>
>> "which enables to"
>>
> Got it.
>>
>>> to specific ports.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze at huawei.com>
>>> ---
[...]
>>> +static u16 get_port(const struct sockaddr *const address)
>>> +{
>>> + /* Gets port value in host byte order. */
>>> + switch (address->sa_family) {
>>> + case AF_UNSPEC:
>>> + case AF_INET: {
>>> + const struct sockaddr_in *const sockaddr =
>>> + (struct sockaddr_in *)address;
>>> + return ntohs(sockaddr->sin_port);
>>> + }
>>> +#if IS_ENABLED(CONFIG_IPV6)
>>> + case AF_INET6: {
>>> + const struct sockaddr_in6 *const sockaddr_ip6 =
>>> + (struct sockaddr_in6 *)address;
>>> + return ntohs(sockaddr_ip6->sin6_port);
>>> + }
>>> +#endif
>>> + }
>>> + WARN_ON_ONCE(1);
>>> + return 0;
>>> +}
>>> +
>>> +static int check_socket_access(struct socket *sock, struct sockaddr *address, int addrlen, u16 port,
>>> + access_mask_t access_request)
>>> +{
>>> + int ret;
>>> + bool allowed = false;
>>> + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>> + const struct landlock_rule *rule;
>>> + access_mask_t handled_access;
>>> + const struct landlock_id id = {
>>> + .key.data = port,
>>> + .type = LANDLOCK_KEY_NET_PORT,
>>> + };
>>> + const struct landlock_ruleset *const domain = get_current_net_domain();
>>> +
>>> + if (WARN_ON_ONCE(!domain))
>>> + return 0;
>>> + if (WARN_ON_ONCE(domain->num_layers < 1))
>>> + return -EACCES;
>>> + /* Check if it's a TCP socket. */
>>> + if (sock->type != SOCK_STREAM)
>>> + return 0;
>>> +
>>> + ret = check_addrlen(address, addrlen);
>>> + if (ret)
>>> + return ret;
>>
>> As explained above, this should be replaced with:
>>
>> if (addrlen < offsetofend(struct sockaddr, sa_family))
>> return -EINVAL;
>>
> Ok.
>>
>>> +
>>> + switch (address->sa_family) {
>>
>>
>> This below block should be moved after the generic switch statement
>> (i.e. once port is checked).
>>
> Do you mean checking address family after a port has been checked??
These specific AF_UNSPEC checks should be in an `if (address->sa_family
== AF_UNSPEC)` block after the generic AF_UNSPEC, AF_INET, and AF_INET6
checks in the address->sa_family switch/case, because the checks and
errors order must be consistent whatever the sa_family. The AF_UNSPEC
checks are really an exception to the AF_INET ones, and should then come
after.
This may look like this:
switch (address->sa_family) {
case AF_UNSPEC:
case AF_INET:
port = ...;
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
port = ...;
break;
#endif
default:
return 0;
}
/* Specific AF_UNSPEC handling. */
if (address->sa_family == AF_UNSPEC) {
...
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
return 0;
...
}
id.key.data = (__force uintptr_t)port;
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
rule = landlock_find_rule(domain, id);
handled_access = landlock_init_layer_masks(
domain, access_request, &layer_masks,
LANDLOCK_KEY_NET_PORT);
if (landlock_unmask_layers(rule, handled_access,
&layer_masks,
ARRAY_SIZE(layer_masks)))
return 0;
return -EACCES;
>
>>
>>
>>> + case AF_UNSPEC:
>>> + /*
>>> + * Connecting to an address with AF_UNSPEC dissolves the TCP
>>> + * association, which have the same effect as closing the
>>> + * connection while retaining the socket object (i.e., the file
>>> + * descriptor). As for dropping privileges, closing
>>> + * connections is always allowed.
>>> + */
>>> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>> + return 0;
>>> +
>>> + /*
>>> + * For compatibility reason, accept AF_UNSPEC for bind
>>> + * accesses (mapped to AF_INET) only if the address is
>>> + * INADDR_ANY (cf. __inet_bind). Checking the address is
>>> + * required to not wrongfully return -EACCES instead of
>>> + * -EAFNOSUPPORT.
>>> + */
>>> + if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>>> + const struct sockaddr_in *const sockaddr =
>>> + (struct sockaddr_in *)address;
>>> +
>>> + if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>>> + return -EAFNOSUPPORT;
>>> + }
>>> +
>>> + fallthrough;
>>
>>
>>
>> case AF_UNSPEC:
>>
>>> + case AF_INET:
>>
>> if (addrlen < sizeof(struct sockaddr_in))
>> return -EINVAL;
>>
>> port = ((struct sockaddr_in *)address)->sin_port;
>> break;
>>
>>
>>> +#if IS_ENABLED(CONFIG_IPV6)
>>> + case AF_INET6:
>>
>> if (addrlen < SIN6_LEN_RFC2133)
>> return -EINVAL;
>>
>> port = ((struct sockaddr_in6 *)address)->sin6_port;
>> break;
>>
>>
>>> +#endif
>>
>> /* Allows unhandled protocols. */
>> default:
>> return 0;
>> }
>>
>> if (address->sa_family == AF_UNSPEC) {
>>
>> // Add here the above AF_UNSPEC checks to be consistent with the
>> EINVAL/EAFNOSUPPORT return ordering.
>>
>> }
>>
>> id.key.data = (__force uintprt_t)port;
>> BUID_BUG_ON(...);
>>
> Will be refactored. Thanks.
More information about the Linux-security-module-archive
mailing list