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.