diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3309f28..5ba938f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,6 +56,8 @@ jobs: - name: Setup Android SDK if: matrix.module == 'saferalloc-natives-android' uses: android-actions/setup-android@v3 + with: + packages: platform-tools - name: Install Android NDK if: matrix.module == 'saferalloc-natives-android' diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 09ca6f2..5f337d6 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -50,6 +50,8 @@ jobs: - name: Setup Android SDK if: matrix.module == 'saferalloc-natives-android' uses: android-actions/setup-android@v3 + with: + packages: platform-tools - name: Install Android NDK if: matrix.module == 'saferalloc-natives-android' @@ -79,6 +81,7 @@ jobs: - name: Publish snapshot to Maven Central snapshots + if: github.event_name == 'push' && github.repository == 'NostrGameEngine/saferalloc' shell: bash env: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} @@ -88,6 +91,12 @@ jobs: run: | ./gradlew --no-daemon -PVERSION_NAME=${VERSION} :${{ matrix.module }}:publishToSonatype + - name: Verify native publication locally + if: github.event_name != 'push' || github.repository != 'NostrGameEngine/saferalloc' + shell: bash + run: | + ./gradlew --no-daemon -PVERSION_NAME=${VERSION} :${{ matrix.module }}:publishToMavenLocal + publish-core: runs-on: ubuntu-latest steps: @@ -118,6 +127,7 @@ jobs: echo "VERSION=${VERSION}" >> "$GITHUB_ENV" - name: Publish core snapshot + if: github.event_name == 'push' && github.repository == 'NostrGameEngine/saferalloc' shell: bash env: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} @@ -126,3 +136,9 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSPHRASE }} run: | ./gradlew --no-daemon -PVERSION_NAME=${VERSION} :saferalloc:publishToSonatype + + - name: Verify core publication locally + if: github.event_name != 'push' || github.repository != 'NostrGameEngine/saferalloc' + shell: bash + run: | + ./gradlew --no-daemon -PVERSION_NAME=${VERSION} :saferalloc:publishToMavenLocal diff --git a/saferalloc/src/test/java/org/ngengine/saferalloc/NativeLibraryExtractionTest.java b/saferalloc/src/test/java/org/ngengine/saferalloc/NativeLibraryExtractionTest.java index 40563e7..40b1025 100644 --- a/saferalloc/src/test/java/org/ngengine/saferalloc/NativeLibraryExtractionTest.java +++ b/saferalloc/src/test/java/org/ngengine/saferalloc/NativeLibraryExtractionTest.java @@ -3,8 +3,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Comparator; import java.util.Set; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -57,14 +61,33 @@ void neverFollowsAPoisonedNativeSymlink() throws IOException { @Test void rejectsReplaceableParent() throws IOException { if (!Files.getFileStore(root).supportsFileAttributeView("posix")) return; - Path replaceable = Files.createTempDirectory("saferalloc-unsafe-"); + // macOS java.io.tmpdir is below a private user directory, so use shared /tmp. + Path replaceable = Files.createTempDirectory(Paths.get("/tmp"), "saferalloc-unsafe-"); try { Files.setPosixFilePermissions(replaceable, - java.nio.file.attribute.PosixFilePermissions.fromString("rwxrwxrwx")); - assertThrows(IOException.class, () -> + PosixFilePermissions.fromString("rwxrwxrwx")); + IOException failure = assertThrows(IOException.class, () -> NativeLibraryExtraction.createDirectory(replaceable, "saferalloc-")); + assertTrue(failure.getMessage().startsWith("Native extraction ancestor is writable by other users:")); } finally { - Files.deleteIfExists(replaceable); + // Preserve the assertion failure even if a regression creates a child directory. + try (Stream paths = Files.walk(replaceable)) { + for (Path path : paths.sorted(Comparator.reverseOrder()).toArray(Path[]::new)) { + Files.deleteIfExists(path); + } + } } } + + @Test + void acceptsWritableParentInsidePrivateAncestor() throws IOException { + if (!Files.getFileStore(root).supportsFileAttributeView("posix")) return; + Files.setPosixFilePermissions(root, PosixFilePermissions.fromString("rwx------")); + Path protectedParent = Files.createDirectory(root.resolve("writable")); + Files.setPosixFilePermissions(protectedParent, PosixFilePermissions.fromString("rwxrwxrwx")); + + Path directory = NativeLibraryExtraction.createDirectory(protectedParent, "saferalloc-"); + assertEquals(protectedParent.toRealPath(), directory.getParent()); + assertEquals(PosixFilePermissions.fromString("rwx------"), Files.getPosixFilePermissions(directory)); + } }