/*
* eset_wap (ESET Web Access Protection module)
* Copyright (C) 1992-2025 ESET, spol. s r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <https://www.gnu.org/licenses/>.
*
* In case of any questions, you can contact us at ESET, spol. s r.o., Einsteinova 24, 851 01 Bratislava, Slovakia.
*/
#include "ewap_connect_data.h"
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "ewap_helpers.h"
#include "ewap_pid_map.h"
#include "ewap_tcp_map.h"
static struct ewap_tcp_map tcp_map;
static struct ewap_pid_map pid_map;
static DEFINE_SPINLOCK(connect_data_lock);
unsigned long connect_data_flags;
static inline void ewap_pr_log_connection(int mask,
const struct ewap_tcp_key *key,
const char *action, pid_t pid,
uid_t uid) {
ewap_pr_log(mask, "connection %s (pid: %d, uid: %u)", action, pid, uid);
ewap_pr_log(mask,
"[sport: %u, saddr: %02x %02x %02x %02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x %02x %02x]",
(unsigned)(uint16_t)(key->port >> 16),
(unsigned)(((unsigned char *)&key->saddr_hi)[0]),
(unsigned)(((unsigned char *)&key->saddr_hi)[1]),
(unsigned)(((unsigned char *)&key->saddr_hi)[2]),
(unsigned)(((unsigned char *)&key->saddr_hi)[3]),
(unsigned)(((unsigned char *)&key->saddr_hi)[4]),
(unsigned)(((unsigned char *)&key->saddr_hi)[5]),
(unsigned)(((unsigned char *)&key->saddr_hi)[6]),
(unsigned)(((unsigned char *)&key->saddr_hi)[7]),
(unsigned)(((unsigned char *)&key->saddr_lo)[0]),
(unsigned)(((unsigned char *)&key->saddr_lo)[1]),
(unsigned)(((unsigned char *)&key->saddr_lo)[2]),
(unsigned)(((unsigned char *)&key->saddr_lo)[3]),
(unsigned)(((unsigned char *)&key->saddr_lo)[4]),
(unsigned)(((unsigned char *)&key->saddr_lo)[5]),
(unsigned)(((unsigned char *)&key->saddr_lo)[6]),
(unsigned)(((unsigned char *)&key->saddr_lo)[7]));
ewap_pr_log(mask,
"[dport: %u, daddr: %02x %02x %02x %02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x %02x %02x]",
(unsigned)(uint16_t)(key->port),
(unsigned)(((unsigned char *)&key->daddr_hi)[0]),
(unsigned)(((unsigned char *)&key->daddr_hi)[1]),
(unsigned)(((unsigned char *)&key->daddr_hi)[2]),
(unsigned)(((unsigned char *)&key->daddr_hi)[3]),
(unsigned)(((unsigned char *)&key->daddr_hi)[4]),
(unsigned)(((unsigned char *)&key->daddr_hi)[5]),
(unsigned)(((unsigned char *)&key->daddr_hi)[6]),
(unsigned)(((unsigned char *)&key->daddr_hi)[7]),
(unsigned)(((unsigned char *)&key->daddr_lo)[0]),
(unsigned)(((unsigned char *)&key->daddr_lo)[1]),
(unsigned)(((unsigned char *)&key->daddr_lo)[2]),
(unsigned)(((unsigned char *)&key->daddr_lo)[3]),
(unsigned)(((unsigned char *)&key->daddr_lo)[4]),
(unsigned)(((unsigned char *)&key->daddr_lo)[5]),
(unsigned)(((unsigned char *)&key->daddr_lo)[6]),
(unsigned)(((unsigned char *)&key->daddr_lo)[7]));
}
static inline void ewap_pr_log_v4_connection(int mask, const char *name,
uint32_t connection,
uint16_t port) {
ewap_pr_log(mask, "v4 %s: %u | port: %u ", name, (unsigned)connection,
(unsigned)port);
}
static inline void ewap_pr_log_v6_connection(int mask, const char *name,
const unsigned char *connection,
uint16_t port) {
ewap_pr_log(mask,
"v6 %s: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x | port: %u ",
name, (unsigned)(connection[0]), (unsigned)(connection[1]),
(unsigned)(connection[2]), (unsigned)(connection[3]),
(unsigned)(connection[4]), (unsigned)(connection[5]),
(unsigned)(connection[6]), (unsigned)(connection[7]),
(unsigned)(connection[8]), (unsigned)(connection[9]),
(unsigned)(connection[10]), (unsigned)(connection[11]),
(unsigned)(connection[12]), (unsigned)(connection[13]),
(unsigned)(connection[14]), (unsigned)(connection[15]),
(unsigned)port);
}
static inline uint32_t convert_port(uint16_t sport, uint16_t dport) {
return ((uint32_t)sport << 16) | (uint32_t)dport;
}
static inline void convert_v6_address(const uint8_t *address, uint64_t *hi,
uint64_t *lo) {
*hi = *(uint64_t *)&address[0];
*lo = *(uint64_t *)&address[8];
}
static inline void convert_v4_address(uint32_t address, uint64_t *hi,
uint64_t *lo) {
*hi = 0;
*lo = cpu_to_be64(0xFFFF00000000ull) |
cpu_to_be64((uint64_t)be32_to_cpu(address));
}
static inline void tcp_key_from_socket_v4(const struct sock *sock,
struct ewap_tcp_key *key) {
convert_v4_address(sock->sk_rcv_saddr, &key->saddr_hi, &key->saddr_lo);
convert_v4_address(sock->sk_daddr, &key->daddr_hi, &key->daddr_lo);
key->port = convert_port(sock->sk_num, ntohs(sock->sk_dport));
ewap_pr_log_v4_connection(EWAP_LOG_CONNECTIONS, "saddr from socket",
sock->sk_rcv_saddr, sock->sk_num);
ewap_pr_log_v4_connection(EWAP_LOG_CONNECTIONS, "daddr from socket",
sock->sk_daddr, ntohs(sock->sk_dport));
}
static inline void tcp_key_from_address_v4(const struct v4_address *address,
struct ewap_tcp_key *key) {
convert_v4_address(address->saddr, &key->saddr_hi, &key->saddr_lo);
convert_v4_address(address->daddr, &key->daddr_hi, &key->daddr_lo);
key->port = convert_port(address->sport, address->dport);
ewap_pr_log_v4_connection(EWAP_LOG_IOCTL, "saddr from ioctl", address->saddr,
address->sport);
ewap_pr_log_v4_connection(EWAP_LOG_IOCTL, "daddr from ioctl", address->daddr,
address->dport);
}
static inline void tcp_key_from_socket_v6(const struct sock *sock,
struct ewap_tcp_key *key) {
const struct in6_addr *saddr = &sock->sk_v6_rcv_saddr;
const struct in6_addr *daddr = &sock->sk_v6_daddr;
convert_v6_address(saddr->in6_u.u6_addr8, &key->saddr_hi, &key->saddr_lo);
convert_v6_address(daddr->in6_u.u6_addr8, &key->daddr_hi, &key->daddr_lo);
key->port = convert_port(sock->sk_num, ntohs(sock->sk_dport));
ewap_pr_log_v6_connection(EWAP_LOG_CONNECTIONS, "saddr from socket",
saddr->in6_u.u6_addr8, sock->sk_num);
ewap_pr_log_v6_connection(EWAP_LOG_CONNECTIONS, "daddr from socket",
daddr->in6_u.u6_addr8, ntohs(sock->sk_dport));
}
static inline void tcp_key_from_address_v6(const struct v6_address *address,
struct ewap_tcp_key *key) {
convert_v6_address(address->saddr, &key->saddr_hi, &key->saddr_lo);
convert_v6_address(address->daddr, &key->daddr_hi, &key->daddr_lo);
key->port = convert_port(address->sport, address->dport);
ewap_pr_log_v6_connection(EWAP_LOG_IOCTL, "saddr from ioctl", address->saddr,
address->sport);
ewap_pr_log_v6_connection(EWAP_LOG_IOCTL, "daddr from ioctl", address->daddr,
address->dport);
}
static bool fill_tcp_key(const struct sock *sock, struct ewap_tcp_key *key) {
switch (sock->sk_family) {
case AF_INET:
tcp_key_from_socket_v4(sock, key);
return true;
case AF_INET6:
tcp_key_from_socket_v6(sock, key);
return true;
default:
return false;
}
return false;
}
int ewap_connect_data_global_init(void) {
int ret = 0;
ewap_static_assert(
sizeof(struct ewap_tcp_node) + sizeof(struct ewap_pid_node) < 256,
"data saved per connection is too large");
ret = ewap_path_allocator_init();
if (unlikely(ret < 0)) {
goto exit;
}
ret = ewap_tcp_node_allocator_init();
if (unlikely(ret != 0)) {
goto error_tcp_map;
}
ret = ewap_pid_map_allocator_init();
if (unlikely(ret != 0)) {
goto error_pid_map;
}
ewap_tcp_map_init(&tcp_map);
ewap_pid_map_init(&pid_map);
goto exit;
error_pid_map:
ewap_tcp_node_allocator_deinit();
error_tcp_map:
ewap_path_allocator_deinit();
exit:
return ret;
}
void ewap_connect_data_global_deinit(void) {
ewap_pid_map_deinit(&pid_map);
ewap_tcp_map_deinit(&tcp_map);
ewap_pid_map_allocator_deinit();
ewap_tcp_node_allocator_deinit();
ewap_path_allocator_deinit();
}
static bool fill_connect_data(const struct ewap_tcp_key *key,
struct ewap_connect_data *data) {
bool ok = false;
struct ewap_tcp_node *node;
ewap_pr_log_connection(EWAP_LOG_IOCTL, key, "requested", -1, 0);
spin_lock_irqsave(&connect_data_lock, connect_data_flags);
node = ewap_tcp_map_get_node(&tcp_map, key);
if (node) {
data->pid = node->pid;
data->uid = node->uid;
data->path = ewap_path_ref(node->path);
ok = true;
}
spin_unlock_irqrestore(&connect_data_lock, connect_data_flags);
if (!ok) {
data->pid = -1;
data->uid = -1;
data->path = NULL;
}
return ok;
}
bool ewap_connect_data_get_v4_connection(const struct v4_address *address,
struct ewap_connect_data *data) {
struct ewap_tcp_key key;
tcp_key_from_address_v4(address, &key);
return fill_connect_data(&key, data);
}
bool ewap_connect_data_get_v6_connection(const struct v6_address *address,
struct ewap_connect_data *data) {
struct ewap_tcp_key key;
tcp_key_from_address_v6(address, &key);
return fill_connect_data(&key, data);
}
bool ewap_connect_data_save_connection(const struct sock *sock, pid_t pid,
uid_t uid) {
struct ewap_tcp_key key;
struct ewap_tcp_node *tcp_node;
struct ewap_pid_node *pid_node;
struct ewap_pid_node *pid_node_in_map;
bool tcp_node_inserted = false;
bool pid_node_inserted = false;
if (!fill_tcp_key(sock, &key)) {
return false;
}
tcp_node = ewap_tcp_node_new(key, pid, uid);
if (unlikely(IS_ERR(tcp_node))) {
ewap_pr_log(EWAP_LOG_ERRORS, "failed to create tcp node: %ld",
PTR_ERR(tcp_node));
return true;
}
pid_node = ewap_pid_node_new(pid);
spin_lock_irqsave(&connect_data_lock, connect_data_flags);
tcp_node_inserted = ewap_tcp_map_add_pid(&tcp_map, tcp_node);
if (unlikely(!tcp_node_inserted)) {
ewap_pr_log(EWAP_LOG_ERRORS,
"failed to insert connection to tcp map: tcp node with same "
"key already in map");
goto end;
}
if (likely(!IS_ERR(pid_node))) {
pid_node_inserted =
ewap_pid_map_add_connection(&pid_map, pid_node, &pid_node_in_map);
} else {
ewap_pr_log(EWAP_LOG_ERRORS, "failed to create pid node: %ld",
PTR_ERR(pid_node));
goto end;
}
list_add(&tcp_node->list, &pid_node_in_map->connections);
ewap_pr_log_connection(EWAP_LOG_CONNECTIONS, &key, "saved", pid, uid);
end:
spin_unlock_irqrestore(&connect_data_lock, connect_data_flags);
if (!tcp_node_inserted && likely(!IS_ERR(tcp_node))) {
ewap_tcp_node_free(tcp_node);
}
if (!pid_node_inserted && likely(!IS_ERR(pid_node))) {
ewap_pid_node_free(pid_node);
}
return true;
}
bool ewap_connect_data_erase_connection(const struct sock *sock) {
struct ewap_tcp_key key;
struct ewap_tcp_node *node;
if (!fill_tcp_key(sock, &key)) {
return false;
}
spin_lock_irqsave(&connect_data_lock, connect_data_flags);
node = ewap_tcp_map_extract_node(&tcp_map, &key);
if (node != NULL) {
list_del_init(&node->list);
}
spin_unlock_irqrestore(&connect_data_lock, connect_data_flags);
if (node != NULL) {
ewap_pr_log_connection(EWAP_LOG_CONNECTIONS, &key, "deleted", node->pid,
node->uid);
ewap_tcp_node_free(node);
} else {
ewap_pr_log_connection(EWAP_LOG_CONNECTIONS, &key, "not found", -1, 0);
}
return true;
}
void ewap_connect_data_erase_all_connections(pid_t pid) {
LIST_HEAD(to_be_removed_list);
struct ewap_tcp_node *tmp;
struct ewap_tcp_node *tcp_node;
struct ewap_pid_node *pid_node;
spin_lock_irqsave(&connect_data_lock, connect_data_flags);
pid_node = ewap_pid_map_extract_node(&pid_map, pid);
if (pid_node) {
list_for_each_entry_safe(tcp_node, tmp, &pid_node->connections, list) {
tcp_node = ewap_tcp_map_extract_node(tcp_node->tcp_map, &tcp_node->key);
if (tcp_node != NULL) {
list_move_tail(&tcp_node->list, &to_be_removed_list);
}
}
ewap_pr_log(EWAP_LOG_PID_REMOVAL, "all connections erased for pid: %d",
pid);
} else {
ewap_pr_log(EWAP_LOG_PID_REMOVAL, "no entries for pid: %d", pid);
}
spin_unlock_irqrestore(&connect_data_lock, connect_data_flags);
list_for_each_entry_safe(tcp_node, tmp, &to_be_removed_list, list) {
list_del_init(&tcp_node->list);
ewap_tcp_node_free(tcp_node);
}
ewap_pid_node_free(pid_node);
}