The common request to block network syscalls while allowing a single HTTPS endpoint presents a nuanced control-flow problem. A seccomp filter operates at the syscall level, inspecting arguments to `connect`, `socket`, etc., but it cannot natively evaluate high-level concepts like TLS or fully-qualified domain names. The filter must be part of a layered strategy.
The architectural goal is twofold: first, to restrict the process to a minimal set of necessary syscalls; second, to constrain the arguments of the networking syscalls themselves. This is achieved by inspecting the `sockaddr` structure pointer passed to `connect`. We must perform a careful dance of pointer validation and content comparison within the seccomp sandbox, which only allows simple, BPF-like operations.
Below is a foundational seccomp-BPF filter for a 64-bit x86_64 process. It assumes you are using `libseccomp` or can translate this BPF. The policy:
* Allows `socket` only for `AF_INET`/`AF_INET6` (TCP/IP).
* Allows `connect` only to a pre-defined IPv4 address and port.
* Denies all other network-family syscalls (`sendto`, `recvfrom`, `bind`, etc.).
```c
// Pseudocode/BPF outline for libseccomp construction
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); // Default allow, then deny
// Start by killing unwanted high-level syscalls.
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), syscall_name, 0);
// For socket, restrict domain to INET/INET6.
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_EQ, AF_INET));
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
SCMP_A0(SCMP_CMP_EQ, AF_INET6));
// For connect, the critical check.
// We must validate that arg2 (addr pointer) points to the allowed struct.
struct sockaddr_in allowed_addr;
inet_pton(AF_INET, "192.0.2.1", &allowed_addr.sin_addr); // Your endpoint IP
allowed_addr.sin_family = AF_INET;
allowed_addr.sin_port = htons(443); // Your endpoint port
// Rule: Allow connect if family, port, and addr (first 4 bytes) match.
// This uses SCMP_CMP_MASKED_EQ to check the 32-bit IP address.
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 3,
SCMP_CMP(0, SCMP_CMP_EQ, AF_INET), // sockfd's domain, implied by socket creation
SCMP_CMP_MEM(2, offsetof(struct sockaddr_in, sin_family), SCMP_CMP_EQ, AF_INET),
SCMP_CMP_MEM(2, offsetof(struct sockaddr_in, sin_port), SCMP_CMP_EQ, allowed_addr.sin_port));
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 3,
SCMP_CMP(0, SCMP_CMP_EQ, AF_INET),
SCMP_CMP_MEM(2, offsetof(struct sockaddr_in, sin_family), SCMP_CMP_EQ, AF_INET),
SCMP_CMP_MEM(2, offsetof(struct sockaddr_in, sin_addr.s_addr), SCMP_CMP_EQ, allowed_addr.sin_addr.s_addr));
seccomp_load(ctx);
```
**Critical Limitations and Layering Requirements:**
* **DNS Resolution:** This filter does not block `getaddrinfo`. That occurs via libc, typically using sockets. You must also block network sockets prior to the DNS resolution step, or force static resolution via `/etc/hosts`. The filter above would only allow the final `connect` to the specific IP.
* **TLS/HTTPS Enforcement:** This filter permits a TCP connection to port 443. It does not guarantee TLS is used. That enforcement must happen at the application layer (e.g., library configurations that mandate TLS).
* **IPv6:** A production filter requires a parallel rule set for `AF_INET6`, comparing the `sin6_addr` struct.
* **Syscall Argument Reading:** The `SCMP_CMP_MEM` instructions read from process memory within the kernel. They are safe but require precise offsets.
In practice, deploy this alongside a namespace-based sandbox (e.g., a network namespace with a single route) and consider an LSM like AppArmor to constrain file accesses to certificates. The seccomp filter is your syscall argument validator, not your network policy manager.
What runtime are you attempting to constrain? The exact syscall set can vary between standard libc and alternative runtimes (e.g., Go, Node.js). I can provide more specific filters if you share the target.
ol
ol