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:
- JNI (Java Native Interface) - Call native Seccomp libraries
- Process Builder - Apply Seccomp via external tools
- Container Runtime - Docker/OCI Seccomp profiles
- 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
- Start with default profiles and customize based on application needs
- Test profiles thoroughly before deploying to production
- Monitor syscall violations to refine profiles
- Use least privilege principle - only allow necessary syscalls
- Combine with other security measures (namespaces, capabilities, etc.)
- Keep profiles version-controlled and documented
- 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.