Seccomp Profiles in Java: Secure Computing Mode Integration

Seccomp (secure computing mode) is a Linux kernel security feature that restricts the system calls available to a process. While Java doesn't have direct Seccomp integration, we can implement it through JNI, native libraries, or container-level configuration.

Understanding Seccomp in Java Context

Approaches for Java Integration:

  1. JNI (Java Native Interface) - Call native Seccomp libraries
  2. Process Builder - Apply Seccomp via external tools
  3. Container Runtime - Docker/OCI Seccomp profiles
  4. JVM Agent - Custom Java agent for syscall filtering

Dependencies and Setup

Maven Configuration:

<dependencies>
<!-- JNA for native access -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>
<!-- Process execution -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
<!-- JSON processing for profile management -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>

Seccomp Profile Configuration

SeccompProfile.java:

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SeccompProfile {
private final String defaultAction;
private final String defaultErrnoRet;
private final List<Architecture> architectures;
private final List<SyscallRule> syscalls;
@Data
@Builder
public static class Architecture {
private final String architecture;
private final List<String> subArchitectures;
}
@Data
@Builder
public static class SyscallRule {
private final List<String> names;
private final String action;
private final List<Arg> args;
@Data
@Builder
public static class Arg {
private final int index;
private final String op;
private final Object value;
private final Object valueTwo;
}
}
public static SeccompProfile createDefaultProfile() {
return SeccompProfile.builder()
.defaultAction("SCMP_ACT_ERRNO")
.defaultErrnoRet("EPERM")
.architectures(Arrays.asList(
Architecture.builder()
.architecture("SCMP_ARCH_X86_64")
.build()
))
.syscalls(createDefaultSyscalls())
.build();
}
public static SeccompProfile createWebAppProfile() {
List<SyscallRule> syscalls = new ArrayList<>();
// Allow essential syscalls for web applications
syscalls.add(createSyscallRule("read", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("write", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("close", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("fstat", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("brk", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("mmap", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("mprotect", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("munmap", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("rt_sigaction", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("rt_sigprocmask", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("clone", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("execve", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("exit_group", "SCMP_ACT_ALLOW"));
return SeccompProfile.builder()
.defaultAction("SCMP_ACT_ERRNO")
.defaultErrnoRet("EPERM")
.architectures(Arrays.asList(
Architecture.builder()
.architecture("SCMP_ARCH_X86_64")
.build()
))
.syscalls(syscalls)
.build();
}
public static SeccompProfile createDatabaseProfile() {
List<SyscallRule> syscalls = new ArrayList<>();
// Database-specific syscalls
syscalls.add(createSyscallRule("read", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("write", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("open", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("close", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("socket", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("connect", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("accept", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("shmget", "SCMP_ACT_ALLOW"));
syscalls.add(createSyscallRule("shmat", "SCMP_ACT_ALLOW"));
return SeccompProfile.builder()
.defaultAction("SCMP_ACT_ERRNO")
.defaultErrnoRet("EPERM")
.architectures(Arrays.asList(
Architecture.builder()
.architecture("SCMP_ARCH_X86_64")
.build()
))
.syscalls(syscalls)
.build();
}
private static List<SyscallRule> createDefaultSyscalls() {
List<SyscallRule> syscalls = new ArrayList<>();
// Minimal set of syscalls for basic functionality
String[] essentialSyscalls = {
"read", "write", "open", "close", "brk", "mmap", "mprotect",
"munmap", "rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
"ioctl", "pread64", "pwrite64", "readv", "writev", "access",
"pipe", "select", "dup", "dup2", "pause", "nanosleep", "getitimer",
"alarm", "setitimer", "getpid", "sendfile", "socket", "connect",
"accept", "sendto", "recvfrom", "sendmsg", "recvmsg", "shutdown",
"bind", "listen", "getsockname", "getpeername", "socketpair",
"setsockopt", "getsockopt", "clone", "fork", "vfork", "execve",
"exit", "wait4", "kill", "uname", "semget", "semop", "semctl",
"shmdt", "msgget", "msgsnd", "msgrcv", "msgctl", "fcntl",
"flock", "fsync", "fdatasync", "truncate", "ftruncate", "getdents",
"getcwd", "chdir", "fchdir", "rename", "mkdir", "rmdir", "creat",
"link", "unlink", "symlink", "readlink", "chmod", "fchmod",
"chown", "fchown", "lchown", "umask", "gettimeofday", "getrlimit",
"getrusage", "sysinfo", "times", "ptrace", "getuid", "syslog",
"getgid", "setuid", "setgid", "geteuid", "getegid", "setpgid",
"getppid", "getpgrp", "setsid", "setreuid", "setregid",
"getgroups", "setgroups", "setresuid", "getresuid", "setresgid",
"getresgid", "getpgid", "setfsuid", "setfsgid", "getsid",
"capget", "capset", "rt_sigpending", "rt_sigtimedwait",
"rt_sigqueueinfo", "rt_sigsuspend", "sigaltstack", "utime",
"mknod", "uselib", "personality", "ustat", "statfs", "fstatfs",
"sysfs", "getpriority", "setpriority", "sched_setparam",
"sched_getparam", "sched_setscheduler", "sched_getscheduler",
"sched_get_priority_max", "sched_get_priority_min",
"sched_rr_get_interval", "mlock", "munlock", "mlockall",
"munlockall", "vhangup", "modify_ldt", "pivot_root", "_sysctl",
"prctl", "arch_prctl", "adjtimex", "setrlimit", "chroot",
"sync", "acct", "settimeofday", "mount", "umount2", "swapon",
"swapoff", "reboot", "sethostname", "setdomainname", "iopl",
"ioperm", "create_module", "init_module", "delete_module",
"get_kernel_syms", "query_module", "quotactl", "nfsservctl",
"getpmsg", "putpmsg", "afs_syscall", "tuxcall", "security",
"gettid", "readahead", "setxattr", "lsetxattr", "fsetxattr",
"getxattr", "lgetxattr", "fgetxattr", "listxattr", "llistxattr",
"flistxattr", "removexattr", "lremovexattr", "fremovexattr",
"tkill", "time", "futex", "sched_setaffinity", "sched_getaffinity",
"set_thread_area", "io_setup", "io_destroy", "io_getevents",
"io_submit", "io_cancel", "get_thread_area", "lookup_dcookie",
"epoll_create", "epoll_ctl_old", "epoll_wait_old", "remap_file_pages",
"getdents64", "set_tid_address", "restart_syscall", "semtimedop",
"fadvise64", "timer_create", "timer_settime", "timer_gettime",
"timer_getoverrun", "timer_delete", "clock_settime", "clock_gettime",
"clock_getres", "clock_nanosleep", "exit_group", "epoll_wait",
"epoll_ctl", "tgkill", "utimes", "vserver", "mbind", "set_mempolicy",
"get_mempolicy", "mq_open", "mq_unlink", "mq_timedsend", "mq_timedreceive",
"mq_notify", "mq_getsetattr", "kexec_load", "waitid", "add_key",
"request_key", "keyctl", "ioprio_set", "ioprio_get", "inotify_init",
"inotify_add_watch", "inotify_rm_watch", "migrate_pages", "openat",
"mkdirat", "mknodat", "fchownat", "futimesat", "newfstatat",
"unlinkat", "renameat", "linkat", "symlinkat", "readlinkat",
"fchmodat", "faccessat", "pselect6", "ppoll", "unshare", "set_robust_list",
"get_robust_list", "splice", "tee", "sync_file_range", "vmsplice",
"move_pages", "utimensat", "epoll_pwait", "signalfd", "timerfd_create",
"eventfd", "fallocate", "timerfd_settime", "timerfd_gettime",
"accept4", "signalfd4", "eventfd2", "epoll_create1", "dup3",
"pipe2", "inotify_init1", "preadv", "pwritev", "rt_tgsigqueueinfo",
"perf_event_open", "recvmmsg", "fanotify_init", "fanotify_mark",
"prlimit64", "name_to_handle_at", "open_by_handle_at", "clock_adjtime",
"syncfs", "sendmmsg", "setns", "getcpu", "process_vm_readv",
"process_vm_writev", "kcmp", "finit_module", "sched_setattr",
"sched_getattr", "renameat2", "seccomp", "getrandom", "memfd_create",
"kexec_file_load", "bpf", "execveat", "userfaultfd", "membarrier",
"mlock2", "copy_file_range", "preadv2", "pwritev2", "pkey_mprotect",
"pkey_alloc", "pkey_free", "statx", "io_pgetevents", "rseq"
};
for (String syscall : essentialSyscalls) {
syscalls.add(createSyscallRule(syscall, "SCMP_ACT_ALLOW"));
}
return syscalls;
}
private static SyscallRule createSyscallRule(String name, String action) {
return SyscallRule.builder()
.names(Arrays.asList(name))
.action(action)
.build();
}
public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
}
public static SeccompProfile fromJson(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, SeccompProfile.class);
}
}

JNA-based Seccomp Integration

SeccompNative.java:

public interface SeccompNative extends Library {
SeccompNative INSTANCE = Native.load("seccomp", SeccompNative.class);
// Seccomp actions
int SCMP_ACT_KILL = 0x00000000;
int SCMP_ACT_TRAP = 0x00030000;
int SCMP_ACT_ERRNO(int errno) { return 0x00050000 | (errno & 0x0000FFFF); }
int SCMP_ACT_TRACE(int msg) { return 0x7FF00000 | (msg & 0x0000FFFF); }
int SCMP_ACT_ALLOW = 0x7FFF0000;
// Architectures
int SCMP_ARCH_NATIVE = -1;
int SCMP_ARCH_X86 = 3;
int SCMP_ARCH_X86_64 = 4;
int SCMP_ARCH_ARM = 40;
int SCMP_ARCH_AARCH64 = 183;
// Filter context
class scmp_filter_ctx extends PointerType {
public scmp_filter_ctx(Pointer address) {
super(address);
}
public scmp_filter_ctx() {
super();
}
}
// Initialize seccomp filter
scmp_filter_ctx seccomp_init(int def_action);
// Release seccomp filter
void seccomp_release(scmp_filter_ctx ctx);
// Reset seccomp filter
void seccomp_reset(scmp_filter_ctx ctx, int def_action);
// Add architecture
int seccomp_arch_add(scmp_filter_ctx ctx, int arch);
// Add rule
int seccomp_rule_add(scmp_filter_ctx ctx, int action, int syscall, int arg_cnt, Object... args);
// Add rule for architecture
int seccomp_rule_add_arch(scmp_filter_ctx ctx, int arch, int action, int syscall, int arg_cnt, Object... args);
// Load filter
int seccomp_load(scmp_filter_ctx ctx);
// Get syscall number from name
int seccomp_syscall_resolve_name(String name);
// Get API level
int seccomp_api_get();
// Set API level
int seccomp_api_set(int level);
}

JnaSeccompService.java:

@Service
@Slf4j
public class JnaSeccompService {
private final SeccompNative seccomp;
private boolean initialized = false;
public JnaSeccompService() {
this.seccomp = SeccompNative.INSTANCE;
initialize();
}
private void initialize() {
try {
int apiLevel = seccomp.seccomp_api_get();
log.info("Seccomp API level: {}", apiLevel);
// Set to latest API level
seccomp.seccomp_api_set(3);
initialized = true;
} catch (Exception e) {
log.warn("Failed to initialize Seccomp native library: {}", e.getMessage());
initialized = false;
}
}
public boolean applyProfile(SeccompProfile profile) {
if (!initialized) {
log.error("Seccomp not initialized");
return false;
}
try {
// Convert default action
int defaultAction = parseAction(profile.getDefaultAction());
// Initialize filter context
SeccompNative.scmp_filter_ctx ctx = seccomp.seccomp_init(defaultAction);
if (ctx == null) {
log.error("Failed to initialize seccomp filter");
return false;
}
// Add architectures
if (profile.getArchitectures() != null) {
for (SeccompProfile.Architecture arch : profile.getArchitectures()) {
int archId = parseArchitecture(arch.getArchitecture());
int result = seccomp.seccomp_arch_add(ctx, archId);
if (result != 0) {
log.warn("Failed to add architecture: {}", arch.getArchitecture());
}
}
}
// Add syscall rules
if (profile.getSyscalls() != null) {
for (SeccompProfile.SyscallRule rule : profile.getSyscalls()) {
if (!addSyscallRule(ctx, rule)) {
log.warn("Failed to add syscall rule: {}", rule.getNames());
}
}
}
// Load the filter
int result = seccomp.seccomp_load(ctx);
if (result == 0) {
log.info("Seccomp profile applied successfully");
return true;
} else {
log.error("Failed to load seccomp filter: {}", result);
return false;
}
} catch (Exception e) {
log.error("Error applying seccomp profile: {}", e.getMessage(), e);
return false;
}
}
private boolean addSyscallRule(SeccompNative.scmp_filter_ctx ctx, SeccompProfile.SyscallRule rule) {
try {
int action = parseAction(rule.getAction());
for (String syscallName : rule.getNames()) {
int syscallNumber = seccomp.seccomp_syscall_resolve_name(syscallName);
if (syscallNumber == -1) {
log.warn("Unknown syscall: {}", syscallName);
continue;
}
int result;
if (rule.getArgs() != null && !rule.getArgs().isEmpty()) {
// Add rule with arguments
result = addSyscallRuleWithArgs(ctx, action, syscallNumber, rule.getArgs());
} else {
// Add simple rule
result = seccomp.seccomp_rule_add(ctx, action, syscallNumber, 0);
}
if (result != 0) {
log.warn("Failed to add rule for syscall {}: {}", syscallName, result);
return false;
}
}
return true;
} catch (Exception e) {
log.error("Error adding syscall rule: {}", e.getMessage(), e);
return false;
}
}
private int addSyscallRuleWithArgs(SeccompNative.scmp_filter_ctx ctx, int action, 
int syscallNumber, List<SeccompProfile.SyscallRule.Arg> args) {
// This would require more complex JNA structures for argument comparison
// For simplicity, we'll add the rule without argument checks
log.debug("Adding syscall {} with {} argument constraints", syscallNumber, args.size());
return seccomp.seccomp_rule_add(ctx, action, syscallNumber, 0);
}
private int parseAction(String action) {
if (action == null) {
return SeccompNative.SCMP_ACT_ERRNO(1); // EPERM
}
switch (action) {
case "SCMP_ACT_ALLOW":
return SeccompNative.SCMP_ACT_ALLOW;
case "SCMP_ACT_KILL":
return SeccompNative.SCMP_ACT_KILL;
case "SCMP_ACT_TRAP":
return SeccompNative.SCMP_ACT_TRAP;
case "SCMP_ACT_ERRNO":
return SeccompNative.SCMP_ACT_ERRNO(1); // EPERM
default:
if (action.startsWith("SCMP_ACT_ERRNO(")) {
// Extract errno from SCMP_ACT_ERRNO(ENOSYS)
String errnoStr = action.substring("SCMP_ACT_ERRNO(".length(), action.length() - 1);
int errno = parseErrno(errnoStr);
return SeccompNative.SCMP_ACT_ERRNO(errno);
}
return SeccompNative.SCMP_ACT_ERRNO(1);
}
}
private int parseArchitecture(String architecture) {
if (architecture == null) {
return SeccompNative.SCMP_ARCH_NATIVE;
}
switch (architecture) {
case "SCMP_ARCH_X86":
return SeccompNative.SCMP_ARCH_X86;
case "SCMP_ARCH_X86_64":
return SeccompNative.SCMP_ARCH_X86_64;
case "SCMP_ARCH_ARM":
return SeccompNative.SCMP_ARCH_ARM;
case "SCMP_ARCH_AARCH64":
return SeccompNative.SCMP_ARCH_AARCH64;
default:
return SeccompNative.SCMP_ARCH_NATIVE;
}
}
private int parseErrno(String errnoStr) {
switch (errnoStr) {
case "EPERM": return 1;
case "ENOENT": return 2;
case "ESRCH": return 3;
case "EINTR": return 4;
case "EIO": return 5;
case "ENXIO": return 6;
case "E2BIG": return 7;
case "ENOEXEC": return 8;
case "EBADF": return 9;
case "ECHILD": return 10;
case "EAGAIN": return 11;
case "ENOMEM": return 12;
case "EACCES": return 13;
case "EFAULT": return 14;
case "ENOTBLK": return 15;
case "EBUSY": return 16;
case "EEXIST": return 17;
case "EXDEV": return 18;
case "ENODEV": return 19;
case "ENOTDIR": return 20;
case "EISDIR": return 21;
case "EINVAL": return 22;
case "ENFILE": return 23;
case "EMFILE": return 24;
case "ENOTTY": return 25;
case "ETXTBSY": return 26;
case "EFBIG": return 27;
case "ENOSPC": return 28;
case "ESPIPE": return 29;
case "EROFS": return 30;
case "EMLINK": return 31;
case "EPIPE": return 32;
case "EDOM": return 33;
case "ERANGE": return 34;
default: return 1; // EPERM
}
}
public boolean isAvailable() {
return initialized;
}
}

Process-based Seccomp Application

ProcessSeccompService.java:

@Service
@Slf4j
public class ProcessSeccompService {
private final ObjectMapper objectMapper;
public ProcessSeccompService() {
this.objectMapper = new ObjectMapper();
}
public boolean applySeccompToProcess(long pid, SeccompProfile profile) {
try {
// Write profile to temporary file
Path profilePath = Files.createTempFile("seccomp", ".json");
Files.write(profilePath, profile.toJson().getBytes());
// Use prctl or external tool to apply seccomp
String[] command = {
"bash", "-c",
String.format("python3 -c \"import seccomp; import json; " +
"profile = json.load(open('%s')); " +
"apply_seccomp_profile(profile, %d)\"", 
profilePath.toString(), pid)
};
CommandLine commandLine = CommandLine.parse(String.join(" ", command));
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(0);
int exitValue = executor.execute(commandLine);
// Clean up
Files.deleteIfExists(profilePath);
return exitValue == 0;
} catch (Exception e) {
log.error("Failed to apply seccomp to process {}: {}", pid, e.getMessage(), e);
return false;
}
}
public boolean launchProcessWithSeccomp(String command, SeccompProfile profile) {
try {
// Write profile to temporary file
Path profilePath = Files.createTempFile("seccomp", ".json");
Files.write(profilePath, profile.toJson().getBytes());
// Use systemd-run or similar to launch process with seccomp
String[] launchCommand = {
"systemd-run",
"--user",
"--scope",
"--property=SystemCallFilter=@default",
"--property=SystemCallArchitectures=native",
"--property=RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6",
command
};
CommandLine commandLine = CommandLine.parse(String.join(" ", launchCommand));
DefaultExecutor executor = new DefaultExecutor();
ExecuteResultHandler resultHandler = new ExecuteResultHandler() {
@Override
public void onProcessComplete(int exitValue) {
log.info("Process completed with exit value: {}", exitValue);
}
@Override
public void onProcessFailed(ExecuteException e) {
log.error("Process failed: {}", e.getMessage(), e);
}
};
executor.execute(commandLine, resultHandler);
// Clean up
Files.deleteIfExists(profilePath);
return true;
} catch (Exception e) {
log.error("Failed to launch process with seccomp: {}", e.getMessage(), e);
return false;
}
}
public SeccompProfile analyzeProcess(long pid) {
try {
// Use strace to analyze syscalls
String[] command = {
"strace", "-c", "-p", String.valueOf(pid)
};
CommandLine commandLine = CommandLine.parse(String.join(" ", command));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
executor.setWatchdog(ExecuteWatchdog.builder().setTimeout(5000).get());
try {
executor.execute(commandLine);
} catch (ExecuteException e) {
// strace typically returns non-zero exit code when interrupted
}
String output = outputStream.toString();
return parseStraceOutput(output);
} catch (Exception e) {
log.error("Failed to analyze process {}: {}", pid, e.getMessage(), e);
return SeccompProfile.createDefaultProfile();
}
}
private SeccompProfile parseStraceOutput(String output) {
List<SeccompProfile.SyscallRule> syscalls = new ArrayList<>();
// Parse strace output to extract syscall names
// This is a simplified parsing - real implementation would be more complex
String[] lines = output.split("\n");
for (String line : lines) {
if (line.trim().matches("^[a-z_]+\\s+\\d+.*")) {
String[] parts = line.trim().split("\\s+");
if (parts.length > 0) {
String syscallName = parts[0];
syscalls.add(SeccompProfile.SyscallRule.builder()
.names(Arrays.asList(syscallName))
.action("SCMP_ACT_ALLOW")
.build());
}
}
}
return SeccompProfile.builder()
.defaultAction("SCMP_ACT_ERRNO")
.syscalls(syscalls)
.build();
}
}

Docker Seccomp Integration

DockerSeccompService.java:

@Service
@Slf4j
public class DockerSeccompService {
private final ObjectMapper objectMapper;
public DockerSeccompService() {
this.objectMapper = new ObjectMapper();
}
public boolean createDockerContainerWithSeccomp(String image, String containerName, 
SeccompProfile profile) {
try {
// Write profile to file
Path profilePath = Files.createTempFile("docker-seccomp", ".json");
Files.write(profilePath, profile.toJson().getBytes());
// Create Docker container with seccomp profile
String[] command = {
"docker", "run", "-d",
"--name", containerName,
"--security-opt", "seccomp=" + profilePath.toString(),
image
};
CommandLine commandLine = CommandLine.parse(String.join(" ", command));
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(0);
int exitValue = executor.execute(commandLine);
// Clean up
Files.deleteIfExists(profilePath);
return exitValue == 0;
} catch (Exception e) {
log.error("Failed to create Docker container with seccomp: {}", e.getMessage(), e);
return false;
}
}
public boolean updateContainerSeccomp(String containerName, SeccompProfile profile) {
try {
// Docker doesn't support updating seccomp profile of running containers
// We need to create a new container with the updated profile
// Get container details
String inspectCommand = String.format("docker inspect %s", containerName);
CommandLine inspectCmd = CommandLine.parse(inspectCommand);
ByteArrayOutputStream inspectOutput = new ByteArrayOutputStream();
PumpStreamHandler inspectHandler = new PumpStreamHandler(inspectOutput);
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(inspectHandler);
try {
executor.execute(inspectCmd);
} catch (ExecuteException e) {
log.error("Failed to inspect container: {}", e.getMessage());
return false;
}
// Parse container configuration and create new container
JsonNode containerConfig = objectMapper.readTree(inspectOutput.toString());
// Implementation would extract and recreate container with new seccomp profile
return true;
} catch (Exception e) {
log.error("Failed to update container seccomp profile: {}", e.getMessage(), e);
return false;
}
}
public SeccompProfile getDockerDefaultProfile() {
try {
// Download Docker's default seccomp profile
String defaultProfileUrl = "https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json";
RestTemplate restTemplate = new RestTemplate();
String profileJson = restTemplate.getForObject(defaultProfileUrl, String.class);
return SeccompProfile.fromJson(profileJson);
} catch (Exception e) {
log.warn("Failed to get Docker default seccomp profile: {}", e.getMessage());
return SeccompProfile.createDefaultProfile();
}
}
public boolean validateSeccompProfile(SeccompProfile profile) {
try {
// Basic validation
if (profile.getDefaultAction() == null) {
log.error("Seccomp profile missing default action");
return false;
}
if (profile.getSyscalls() == null || profile.getSyscalls().isEmpty()) {
log.warn("Seccomp profile has no syscall rules");
}
// Validate syscall names
for (SeccompProfile.SyscallRule rule : profile.getSyscalls()) {
if (rule.getNames() == null || rule.getNames().isEmpty()) {
log.error("Syscall rule missing names");
return false;
}
if (rule.getAction() == null) {
log.error("Syscall rule missing action");
return false;
}
}
return true;
} catch (Exception e) {
log.error("Seccomp profile validation failed: {}", e.getMessage(), e);
return false;
}
}
}

Java Agent for Seccomp

SeccompAgent.java:

public class SeccompAgent {
private static volatile SeccompProfile currentProfile;
private static volatile boolean enabled = false;
public static void premain(String args, Instrumentation inst) {
log.info("Initializing Seccomp Java Agent");
try {
if (args != null) {
currentProfile = SeccompProfile.fromJson(args);
} else {
currentProfile = SeccompProfile.createDefaultProfile();
}
enabled = initializeSeccomp();
if (enabled) {
log.info("Seccomp agent initialized successfully");
} else {
log.warn("Seccomp agent initialization failed");
}
} catch (Exception e) {
log.error("Failed to initialize Seccomp agent: {}", e.getMessage(), e);
}
}
public static void agentmain(String args, Instrumentation inst) {
premain(args, inst);
}
private static boolean initializeSeccomp() {
try {
// Use JNA to apply seccomp profile
JnaSeccompService seccompService = new JnaSeccompService();
if (seccompService.isAvailable()) {
return seccompService.applyProfile(currentProfile);
}
return false;
} catch (Exception e) {
log.error("Seccomp initialization failed: {}", e.getMessage(), e);
return false;
}
}
public static void updateProfile(SeccompProfile newProfile) {
currentProfile = newProfile;
if (enabled) {
initializeSeccomp();
}
}
public static SeccompProfile getCurrentProfile() {
return currentProfile;
}
public static boolean isEnabled() {
return enabled;
}
private static final Logger log = LoggerFactory.getLogger(SeccompAgent.class);
}

Seccomp Management Service

SeccompManager.java:

@Service
@Slf4j
public class SeccompManager {
private final JnaSeccompService jnaSeccompService;
private final ProcessSeccompService processSeccompService;
private final DockerSeccompService dockerSeccompService;
private final Map<String, SeccompProfile> profileTemplates;
public SeccompManager(JnaSeccompService jnaSeccompService,
ProcessSeccompService processSeccompService,
DockerSeccompService dockerSeccompService) {
this.jnaSeccompService = jnaSeccompService;
this.processSeccompService = processSeccompService;
this.dockerSeccompService = dockerSeccompService;
this.profileTemplates = loadProfileTemplates();
}
public SeccompApplicationResult applySeccomp(SeccompApplicationRequest request) {
try {
SeccompProfile profile = resolveProfile(request);
if (!dockerSeccompService.validateSeccompProfile(profile)) {
return SeccompApplicationResult.failure("Invalid seccomp profile");
}
boolean success = false;
String method = "";
switch (request.getApplicationMethod()) {
case "JNA":
success = jnaSeccompService.applyProfile(profile);
method = "JNA";
break;
case "PROCESS":
success = processSeccompService.applySeccompToProcess(
request.getPid(), profile);
method = "PROCESS";
break;
case "DOCKER":
success = dockerSeccompService.createDockerContainerWithSeccomp(
request.getImage(), request.getContainerName(), profile);
method = "DOCKER";
break;
default:
return SeccompApplicationResult.failure(
"Unknown application method: " + request.getApplicationMethod());
}
if (success) {
log.info("Seccomp profile applied successfully using method: {}", method);
return SeccompApplicationResult.success(method, profile);
} else {
return SeccompApplicationResult.failure(
"Failed to apply seccomp profile using method: " + method);
}
} catch (Exception e) {
log.error("Seccomp application failed: {}", e.getMessage(), e);
return SeccompApplicationResult.failure("Application error: " + e.getMessage());
}
}
public SeccompProfile analyzeApplication(String applicationPath) {
try {
// Launch application and analyze its syscall usage
Process process = Runtime.getRuntime().exec(applicationPath);
long pid = process.pid();
// Give application time to initialize
Thread.sleep(2000);
SeccompProfile profile = processSeccompService.analyzeProcess(pid);
// Terminate the process
process.destroy();
return profile;
} catch (Exception e) {
log.error("Application analysis failed: {}", e.getMessage(), e);
return SeccompProfile.createDefaultProfile();
}
}
public SeccompProfile createCustomProfile(List<String> allowedSyscalls, 
List<String> deniedSyscalls) {
List<SeccompProfile.SyscallRule> rules = new ArrayList<>();
// Add allowed syscalls
for (String syscall : allowedSyscalls) {
rules.add(SeccompProfile.SyscallRule.builder()
.names(Arrays.asList(syscall))
.action("SCMP_ACT_ALLOW")
.build());
}
// Add denied syscalls (though default action will handle them)
for (String syscall : deniedSyscalls) {
rules.add(SeccompProfile.SyscallRule.builder()
.names(Arrays.asList(syscall))
.action("SCMP_ACT_ERRNO")
.build());
}
return SeccompProfile.builder()
.defaultAction("SCMP_ACT_ERRNO")
.defaultErrnoRet("EPERM")
.syscalls(rules)
.build();
}
public boolean testSeccompProfile(SeccompProfile profile) {
try {
// Create a test container with the profile
return dockerSeccompService.createDockerContainerWithSeccomp(
"alpine", "seccomp-test-" + System.currentTimeMillis(), profile);
} catch (Exception e) {
log.error("Seccomp profile test failed: {}", e.getMessage(), e);
return false;
}
}
private SeccompProfile resolveProfile(SeccompApplicationRequest request) {
if (request.getCustomProfile() != null) {
return request.getCustomProfile();
}
if (request.getProfileTemplate() != null) {
SeccompProfile template = profileTemplates.get(request.getProfileTemplate());
if (template != null) {
return template;
}
}
// Default to web application profile
return SeccompProfile.createWebAppProfile();
}
private Map<String, SeccompProfile> loadProfileTemplates() {
Map<String, SeccompProfile> templates = new HashMap<>();
templates.put("webapp", SeccompProfile.createWebAppProfile());
templates.put("database", SeccompProfile.createDatabaseProfile());
templates.put("minimal", SeccompProfile.createDefaultProfile());
templates.put("docker-default", dockerSeccompService.getDockerDefaultProfile());
return templates;
}
@Data
@Builder
public static class SeccompApplicationRequest {
private String applicationMethod; // JNA, PROCESS, DOCKER
private String profileTemplate;
private SeccompProfile customProfile;
private Long pid;
private String image;
private String containerName;
}
@Data
@Builder
public static class SeccompApplicationResult {
private boolean success;
private String message;
private String method;
private SeccompProfile appliedProfile;
public static SeccompApplicationResult success(String method, SeccompProfile profile) {
return SeccompApplicationResult.builder()
.success(true)
.message("Seccomp profile applied successfully")
.method(method)
.appliedProfile(profile)
.build();
}
public static SeccompApplicationResult failure(String message) {
return SeccompApplicationResult.builder()
.success(false)
.message(message)
.build();
}
}
}

REST API for Seccomp Management

SeccompController.java:

@RestController
@RequestMapping("/api/seccomp")
@Slf4j
public class SeccompController {
private final SeccompManager seccompManager;
public SeccompController(SeccompManager seccompManager) {
this.seccompManager = seccompManager;
}
@PostMapping("/apply")
public ResponseEntity<SeccompApplicationResult> applySeccomp(
@RequestBody SeccompApplicationRequest request) {
SeccompApplicationResult result = seccompManager.applySeccomp(request);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}
}
@PostMapping("/analyze")
public ResponseEntity<SeccompProfile> analyzeApplication(
@RequestBody AnalyzeRequest request) {
SeccompProfile profile = seccompManager.analyzeApplication(request.getApplicationPath());
return ResponseEntity.ok(profile);
}
@PostMapping("/profile/create")
public ResponseEntity<SeccompProfile> createProfile(
@RequestBody CreateProfileRequest request) {
SeccompProfile profile = seccompManager.createCustomProfile(
request.getAllowedSyscalls(), request.getDeniedSyscalls());
return ResponseEntity.ok(profile);
}
@PostMapping("/profile/test")
public ResponseEntity<TestResult> testProfile(@RequestBody SeccompProfile profile) {
boolean success = seccompManager.testSeccompProfile(profile);
TestResult result = TestResult.builder()
.success(success)
.message(success ? "Profile test passed" : "Profile test failed")
.build();
return ResponseEntity.ok(result);
}
@GetMapping("/profiles/templates")
public ResponseEntity<Map<String, String>> getProfileTemplates() {
Map<String, String> templates = Map.of(
"webapp", "Web Application Profile",
"database", "Database Profile", 
"minimal", "Minimal Profile",
"docker-default", "Docker Default Profile"
);
return ResponseEntity.ok(templates);
}
@GetMapping("/status")
public ResponseEntity<StatusResponse> getStatus() {
JnaSeccompService jnaService = new JnaSeccompService();
StatusResponse status = StatusResponse.builder()
.jnaAvailable(jnaService.isAvailable())
.dockerAvailable(checkDockerAvailable())
.systemInfo(getSystemInfo())
.build();
return ResponseEntity.ok(status);
}
// DTO classes
@Data
public static class AnalyzeRequest {
private String applicationPath;
}
@Data
public static class CreateProfileRequest {
private List<String> allowedSyscalls;
private List<String> deniedSyscalls;
}
@Data
@Builder
public static class TestResult {
private boolean success;
private String message;
}
@Data
@Builder
public static class StatusResponse {
private boolean jnaAvailable;
private boolean dockerAvailable;
private Map<String, Object> systemInfo;
}
private boolean checkDockerAvailable() {
try {
Process process = Runtime.getRuntime().exec("docker --version");
return process.waitFor() == 0;
} catch (Exception e) {
return false;
}
}
private Map<String, Object> getSystemInfo() {
Map<String, Object> info = new HashMap<>();
info.put("osName", System.getProperty("os.name"));
info.put("osArch", System.getProperty("os.arch"));
info.put("javaVersion", System.getProperty("java.version"));
info.put("user", System.getProperty("user.name"));
return info;
}
}

Best Practices

  1. Start with default profiles and customize based on application needs
  2. Test profiles thoroughly before deploying to production
  3. Monitor syscall violations to refine profiles
  4. Use least privilege principle - only allow necessary syscalls
  5. Combine with other security measures (namespaces, capabilities, etc.)
  6. Keep profiles version-controlled and documented
  7. Regularly update profiles based on application changes

Conclusion

Seccomp integration in Java provides:

  • System call restriction for enhanced security
  • Multiple integration methods (JNA, process, Docker, agent)
  • Profile management with templates and customization
  • Application analysis for profile generation
  • Container integration for modern deployments

While Java doesn't have native Seccomp support, the approaches demonstrated enable Java applications to leverage Linux kernel security features effectively, providing defense-in-depth security for critical applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper