google-site-verification: google61fe8ba583a51912.html
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