Article
As Java applications become more security-sensitive, traditional container isolation often proves insufficient against sophisticated attacks. Landlock Linux Security Module (LSM) provides a powerful, programmatic way to sandbox applications at the filesystem level, offering Java developers fine-grained control over what their applications can access, even within already-containerized environments.
What is Landlock LSM?
Landlock is a Linux Security Module that enables unprivileged processes to create filesystem sandboxes. Unlike traditional LSMs that require root privileges, Landlock allows applications to voluntarily restrict their own access rights. For Java applications, this means you can enforce the principle of least privilege at runtime, limiting file and directory access to only what's strictly necessary.
Why Landlock Matters for Java Applications
- Defense in Depth: Even if an attacker compromises your Java application, Landlock restrictions prevent lateral movement and data exfiltration.
- Zero-Trust Filesystem Access: Enforces that Java applications can only access explicitly allowed paths, reducing the impact of path traversal vulnerabilities.
- Container Security Enhancement: Complements container isolation by adding an additional layer of filesystem security within the container itself.
- Runtime Security: Unlike static analysis, Landlock enforces restrictions at runtime, catching zero-day exploits and unexpected behavior.
Landlock Capabilities Relevant to Java
Landlock restricts filesystem operations through access rights:
- File operations:
read,write,execute,create,remove - Directory operations:
traverse,remove_dir,make_dir - File attribute operations:
getattr,setattr,chmod,chown
Implementing Landlock in Java Applications
1. Native Integration via JNI
Since Landlock is a Linux kernel feature, Java applications need to use JNI to interact with it:
// Landlock JNI wrapper public class LandlockSandbox { static { System.loadLibrary("landlockjni"); } // Native method to apply Landlock rules public native void applySandbox(String[] allowedPaths, int[] accessRights); // Helper method for common Java scenarios public void restrictJavaApplication() { // Define exactly what the Java application needs String[] allowedPaths = { "/app/myapp.jar", // Application JAR "/tmp", // Temporary files "/var/log/myapp", // Log directory "/etc/ssl/certs", // SSL certificates "/usr/lib/jvm", // JVM installation "/proc/self/fd/1", // stdout "/proc/self/fd/2" // stderr }; int[] accessRights = { 0x1 | 0x2, // READ | WRITE for JAR 0x2 | 0x4, // WRITE | CREATE for /tmp 0x2 | 0x4, // WRITE | CREATE for logs 0x1, // READ for SSL certs 0x1 | 0x8, // READ | EXECUTE for JVM 0x2, // WRITE for stdout/stderr }; applySandbox(allowedPaths, accessRights); } } 2. Native C Implementation
The JNI native code that implements Landlock rules:
// landlock_jni.c #include <jni.h> #include <linux/landlock.h> #include <sys/syscall.h> #include <unistd.h> #include <errno.h> JNIEXPORT void JNICALL Java_LandlockSandbox_applySandbox (JNIEnv *env, jobject obj, jobjectArray paths, jintArray rights) { // Create Landlock ruleset struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM, }; int ruleset_fd = syscall(SYS_landlock_create_ruleset, &ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) { // Handle error - Landlock not supported or insufficient permissions return; } // Get array sizes jsize path_count = (*env)->GetArrayLength(env, paths); jsize right_count = (*env)->GetArrayLength(env, rights); for (int i = 0; i < path_count && i < right_count; i++) { jstring path_str = (*env)->GetObjectArrayElement(env, paths, i); jint right = (*env)->GetIntArrayElements(env, rights, NULL)[i]; const char *path = (*env)->GetStringUTFChars(env, path_str, NULL); struct landlock_path_beneath_attr path_attr = { .allowed_access = right, .parent_fd = open(path, O_PATH | O_CLOEXEC), }; if (path_attr.parent_fd >= 0) { syscall(SYS_landlock_add_rule, ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_attr, 0); close(path_attr.parent_fd); } (*env)->ReleaseStringUTFChars(env, path_str, path); } // Apply the ruleset to current process syscall(SYS_landlock_restrict_self, ruleset_fd, 0); close(ruleset_fd); } 3. Maven Build Configuration
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>17</source> <target>17</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>native-maven-plugin</artifactId> <executions> <execution> <id>compile-landlock-jni</id> <goals> <goal>compile</goal> </goals> <configuration> <sources> <source> <directory>src/main/native</directory> <includes> <include>**/*.c</include> </includes> </source> </sources> <compilerStartOptions> <compilerStartOption>-I${java.home}/../include</compilerStartOption> <compilerStartOption>-I${java.home}/../include/linux</compilerStartOption> <compilerStartOption>-fPIC</compilerStartOption> </compilerStartOptions> </configuration> </execution> </executions> </plugin> </plugins> </build> Security Profiles for Java Applications
1. Web Application Profile
public class WebAppLandlockProfile implements LandlockProfile { public void apply() { LandlockSandbox sandbox = new LandlockSandbox(); String[] paths = { "/app/application.war", // WAR file "/tmp", // Temporary files "/var/log/tomcat", // Tomcat logs "/etc/ssl/certs", // SSL certificates "/usr/share/zoneinfo", // Timezone data "/proc/self/fd/1", // stdout "/proc/self/fd/2", // stderr "/sys/fs/cgroup", // CGroup info (optional) "/dev/urandom", // Random source "/dev/null" // Null device }; int[] rights = { 0x1, // READ for WAR 0x2 | 0x4, // WRITE | CREATE for /tmp 0x2, // WRITE for logs 0x1, // READ for SSL certs 0x1, // READ for timezone 0x2, // WRITE for stdout/stderr 0x1, // READ for cgroups 0x1, // READ for /dev/urandom 0x2 // WRITE for /dev/null }; sandbox.applySandbox(paths, rights); } } 2. Batch Processing Profile
public class BatchJobLandlockProfile implements LandlockProfile { public void apply() { LandlockSandbox sandbox = new LandlockSandbox(); String[] paths = { "/app/batch-job.jar", "/input/data", // Input directory "/output/results", // Output directory "/tmp", "/var/log/batch", "/etc/passwd", // For user lookup "/etc/group", // For group lookup "/dev/urandom" }; int[] rights = { 0x1, // READ for JAR 0x1, // READ for input 0x2 | 0x4, // WRITE | CREATE for output 0x2 | 0x4, // WRITE | CREATE for /tmp 0x2, // WRITE for logs 0x1, // READ for passwd 0x1, // READ for group 0x1 // READ for /dev/urandom }; sandbox.applySandbox(paths, rights); } } Integration with Containerized Java Applications
1. Dockerfile with Landlock Support
FROM eclipse-temurin:17-jdk AS builder # Install build dependencies RUN apt-get update && apt-get install -y gcc libc6-dev WORKDIR /app COPY . . # Build Java application and JNI library RUN javac -h src/main/native src/main/java/com/company/LandlockSandbox.java RUN gcc -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \ -shared -fPIC -o liblandlockjni.so src/main/native/landlock_jni.c # Runtime stage FROM eclipse-temurin:17-jre # Copy JNI library COPY --from=builder /app/liblandlockjni.so /usr/lib/ # Copy application COPY --from=builder /app/target/myapp.jar /app.jar # Ensure Landlock is available (requires Linux kernel 5.13+) RUN grep -q "landlock" /proc/self/status || echo "WARNING: Landlock not available" # Run with Landlock sandbox ENTRYPOINT ["java", "-Djava.library.path=/usr/lib", "-jar", "/app.jar"] 2. Kubernetes Security Context
apiVersion: apps/v1 kind: Deployment metadata: name: java-landlock-app spec: template: spec: containers: - name: java-app image: myapp:landlock securityContext: privileged: false capabilities: drop: ["ALL"] seccompProfile: type: RuntimeDefault # Landlock requires specific kernel features env: - name: JAVA_OPTS value: "-Djava.library.path=/usr/lib -XX:+UseContainerSupport"
Best Practices for Java Teams
1. Gradual Implementation Strategy
public class LandlockEnforcer { public static void enableSandbox() { try { LandlockSandbox sandbox = new LandlockSandbox(); sandbox.restrictJavaApplication(); System.out.println("Landlock sandbox enabled successfully"); } catch (UnsatisfiedLinkError e) { System.out.println("Landlock not available: " + e.getMessage()); // Continue without sandbox - fail open for compatibility } catch (Exception e) { System.err.println("Failed to enable Landlock: " + e.getMessage()); // Log but continue - security shouldn't break functionality } } // Call this early in application startup static { enableSandbox(); } } 2. Testing Landlock Restrictions
public class LandlockTest { @Test public void testSandboxRestrictions() { // Test allowed paths assertTrue(canRead("/app/myapp.jar")); assertTrue(canWrite("/tmp/test.txt")); // Test disallowed paths assertFalse(canRead("/etc/shadow")); assertFalse(canWrite("/root/.bashrc")); assertFalse(canExecute("/bin/bash")); } private boolean canRead(String path) { try { Files.readAllBytes(Paths.get(path)); return true; } catch (AccessDeniedException e) { return false; } catch (Exception e) { return false; } } } 3. Monitoring and Logging
public class LandlockMonitor { private static final Logger logger = LoggerFactory.getLogger(LandlockMonitor.class); public void monitorAccessAttempts() { // Use Linux audit subsystem or eBPF to monitor access attempts // This helps identify legitimate needs vs. attack attempts try { Process process = Runtime.getRuntime().exec(new String[]{ "auditctl", "-a", "always,exit", "-F", "arch=b64", "-S", "openat", "-S", "execve", "-k", "landlock_java" }); int exitCode = process.waitFor(); if (exitCode == 0) { logger.info("Landlock audit rules installed"); } } catch (Exception e) { logger.warn("Failed to install audit rules: {}", e.getMessage()); } } } Limitations and Considerations
1. Kernel Requirements
# Check Landlock availability grep -q "landlock" /proc/self/status && echo "Landlock available" || echo "Landlock not available" # Minimum kernel version: 5.13 for basic support uname -r
2. Java-Specific Challenges
- Dynamic Class Loading: Java applications that load classes at runtime may need access to unexpected paths
- JNI Libraries: Native libraries may require additional filesystem access
- Reflection: Some Java frameworks use reflection to access files, which might be blocked
- Spring Boot DevTools: Development-time file watching features conflict with strict sandboxing
3. Fallback Strategies
public class AdaptiveSandbox { private enum SandboxMode { LANDBOX, APPARMOR, SELINUX, NONE } public static SandboxMode detectAvailableSandbox() { if (isLandlockAvailable()) { return SandboxMode.LANDBOX; } else if (isAppArmorAvailable()) { return SandboxMode.APPARMOR; } else if (isSELinuxAvailable()) { return SandboxMode.SELINUX; } else { return SandboxMode.NONE; } } public static void applyBestSandbox() { switch (detectAvailableSandbox()) { case LANDBOX: applyLandlockSandbox(); break; case APPARMOR: applyAppArmorProfile(); break; case SELINUX: applySELinuxContext(); break; case NONE: logWarning("No LSM available - running without sandbox"); break; } } } Conclusion
Landlock LSM represents a significant advancement in application self-protection for Java developers. By allowing applications to voluntarily restrict their own filesystem access, it provides a powerful tool for implementing the principle of least privilege at runtime. While implementing Landlock in Java requires JNI and careful consideration of access patterns, the security benefits are substantial: reduced attack surface, containment of successful attacks, and enhanced compliance with security best practices.
For Java teams running security-sensitive applications, Landlock offers a way to add defense-in-depth that's complementary to containerization and other security measures. As Linux distributions adopt newer kernels with Landlock support, this technology will become an increasingly important part of the Java security toolkit, enabling applications to protect themselves even in compromised environments.
Title: Advanced Java Security: OAuth 2.0, Strong Authentication & Cryptographic Best Practices
Summary: These articles collectively explain how modern Java systems implement secure authentication, authorization, and password protection using industry-grade standards like OAuth 2.0 extensions, mutual TLS, and advanced password hashing algorithms, along with cryptographic best practices for generating secure randomness.
Links with explanations:
https://macronepal.com/blog/dpop-oauth-demonstrating-proof-of-possession-in-java-binding-tokens-to-clients/ (Explains DPoP, which binds OAuth tokens to a specific client so stolen tokens cannot be reused by attackers)
https://macronepal.com/blog/beyond-bearer-tokens-implementing-mutual-tls-for-strong-authentication-in-java/ (Covers mTLS, where both client and server authenticate each other using certificates for stronger security than bearer tokens)
https://macronepal.com/blog/oauth-2-0-token-exchange-in-java-implementing-rfc-8693-for-modern-identity-flows/ (Explains token exchange, allowing secure swapping of access tokens between services in distributed systems)
https://macronepal.com/blog/true-randomness-integrating-hardware-rngs-for-cryptographically-secure-java-applications/ (Discusses hardware-based random number generation for producing truly secure cryptographic keys)
https://macronepal.com/blog/the-password-hashing-dilemma-bcrypt-vs-pbkdf2-in-java/ (Compares BCrypt and PBKDF2 for password hashing and their resistance to brute-force attacks)
https://macronepal.com/blog/scrypt-implementation-in-java-memory-hard-password-hashing-for-jvm-applications/ (Explains Scrypt, a memory-hard hashing algorithm designed to resist GPU/ASIC attacks)
https://macronepal.com/blog/modern-password-security-implementing-argon2-in-java-applications/ (Covers Argon2, a modern and highly secure password hashing algorithm with strong memory-hard protections)