This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit b2dbb1236ae8e858d6f402bff737ec180d629d3e
Author: Richard Zowalla <[email protected]>
AuthorDate: Tue Oct 13 08:49:29 2026 +0200

    Keep RAFPassivater files in a private temp directory (#3082)
    
    Refuse pre-existing files there, and fix the file id and size recorded
    per entry so activate reads back what passivate wrote.
    
    (cherry picked from commit 3205f69fa69e41bf4112e9c85ad29f08a56ae03a)
---
 .../apache/openejb/core/managed/RAFPassivater.java |  50 +++---
 .../openejb/core/stateful/RAFPassivater.java       |  49 +++---
 .../openejb/core/managed/RAFPassivaterTest.java    | 174 +++++++++++++++++++++
 .../openejb/core/stateful/RAFPassivaterTest.java   | 174 +++++++++++++++++++++
 4 files changed, 404 insertions(+), 43 deletions(-)

diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
index 4d1b026de7..0f40a3b724 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/core/managed/RAFPassivater.java
@@ -22,7 +22,11 @@ import org.apache.openejb.spi.Serializer;
 import org.apache.openejb.util.JavaSecurityManagers;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.Map;
@@ -34,6 +38,7 @@ public class RAFPassivater implements PassivationStrategy {
 
     int fileID;
     Hashtable masterTable = new Hashtable();
+    private Path directory;
 
     static class Pointer {
         int fileid;
@@ -50,28 +55,32 @@ public class RAFPassivater implements PassivationStrategy {
     public void init(final Properties props) throws SystemException {
     }
 
+    // the files are deserialized on activation, so keep them in an owner-only
+    // directory with an unpredictable name instead of the shared tmp dir
+    private File passivationFile(final int id) throws IOException {
+        if (directory == null) {
+            final String tmp = 
JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + 
"tmp");
+            directory = Files.createTempDirectory(Paths.get(tmp), 
"openejb-passivation-");
+        }
+        return directory.resolve("passivation" + id + ".ser").toFile();
+    }
+
+    private File createPassivationFile(final int id) throws IOException {
+        final File file = passivationFile(id);
+        // fails if the file already exists and never follows a link
+        Files.createFile(file.toPath());
+        return file;
+    }
+
     public synchronized void passivate(final Map stateTable)
         throws SystemException {
-        try (final RandomAccessFile ras =
-                     new 
RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", 
File.separator + "tmp") +
-                             File.separator + "passivation" + fileID + ".ser", 
"rw")) {
-            fileID++;
-
+        final int id = fileID++;
+        try (final RandomAccessFile ras = new 
RandomAccessFile(createPassivationFile(id), "rw")) {
             final Iterator iterator = stateTable.keySet().iterator();
-            Pointer lastPointer = null;
             while (iterator.hasNext()) {
-                final Object id = iterator.next();
-                final Object obj = stateTable.get(id);
-                final byte[] bytes = Serializer.serialize(obj);
-                final long filepointer = ras.getFilePointer();
-
-                if (lastPointer == null) {
-                    lastPointer = new Pointer(fileID, filepointer, (int) 
filepointer);
-                } else {
-                    lastPointer = new Pointer(fileID, filepointer, (int) 
(filepointer - lastPointer.filepointer));
-                }
-
-                masterTable.put(id, lastPointer);
+                final Object key = iterator.next();
+                final byte[] bytes = Serializer.serialize(stateTable.get(key));
+                masterTable.put(key, new Pointer(id, ras.getFilePointer(), 
bytes.length));
                 ras.write(bytes);
             }
         } catch (final Exception e) {
@@ -87,13 +96,10 @@ public class RAFPassivater implements PassivationStrategy {
             return null;
         }
 
-        try (final RandomAccessFile ras =
-                     new 
RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", 
File.separator + "tmp") +
-                             File.separator + "passivation" + pointer.fileid + 
".ser", "r")) {
+        try (final RandomAccessFile ras = new 
RandomAccessFile(passivationFile(pointer.fileid), "r")) {
             final byte[] bytes = new byte[pointer.bytesize];
             ras.seek(pointer.filepointer);
             ras.readFully(bytes);
-            ras.close();
             return Serializer.deserialize(bytes);
         } catch (final Exception e) {
             throw new SystemException(e);
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
index 5058663a41..6a1cbbde2c 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/RAFPassivater.java
@@ -22,7 +22,11 @@ import org.apache.openejb.spi.Serializer;
 import org.apache.openejb.util.JavaSecurityManagers;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -32,6 +36,7 @@ public class RAFPassivater implements PassivationStrategy {
 
     int fileID;
     HashMap masterTable = new HashMap();
+    private Path directory;
 
     static class Pointer {
 
@@ -50,29 +55,33 @@ public class RAFPassivater implements PassivationStrategy {
     public void init(final Properties props) throws SystemException {
     }
 
+    // the files are deserialized on activation, so keep them in an owner-only
+    // directory with an unpredictable name instead of the shared tmp dir
+    private File passivationFile(final int id) throws IOException {
+        if (directory == null) {
+            final String tmp = 
JavaSecurityManagers.getSystemProperty("java.io.tmpdir", File.separator + 
"tmp");
+            directory = Files.createTempDirectory(Paths.get(tmp), 
"openejb-passivation-");
+        }
+        return directory.resolve("passivation" + id + ".ser").toFile();
+    }
+
+    private File createPassivationFile(final int id) throws IOException {
+        final File file = passivationFile(id);
+        // fails if the file already exists and never follows a link
+        Files.createFile(file.toPath());
+        return file;
+    }
+
     @Override
     public synchronized void passivate(final Map stateTable)
         throws SystemException {
-        try(final RandomAccessFile ras = new RandomAccessFile(
-                JavaSecurityManagers.getSystemProperty("java.io.tmpdir", 
File.separator + "tmp") +
-                        File.separator + "passivation" + fileID + ".ser", 
"rw")) {
-            fileID++;
-
+        final int id = fileID++;
+        try (final RandomAccessFile ras = new 
RandomAccessFile(createPassivationFile(id), "rw")) {
             final Iterator iterator = stateTable.keySet().iterator();
-            Pointer lastPointer = null;
             while (iterator.hasNext()) {
-                final Object id = iterator.next();
-                final Object obj = stateTable.get(id);
-                final byte[] bytes = Serializer.serialize(obj);
-                final long filepointer = ras.getFilePointer();
-
-                if (lastPointer == null) {
-                    lastPointer = new Pointer(fileID, filepointer, (int) 
filepointer);
-                } else {
-                    lastPointer = new Pointer(fileID, filepointer, (int) 
(filepointer - lastPointer.filepointer));
-                }
-
-                masterTable.put(id, lastPointer);
+                final Object key = iterator.next();
+                final byte[] bytes = Serializer.serialize(stateTable.get(key));
+                masterTable.put(key, new Pointer(id, ras.getFilePointer(), 
bytes.length));
                 ras.write(bytes);
             }
         } catch (final Exception e) {
@@ -89,9 +98,7 @@ public class RAFPassivater implements PassivationStrategy {
             return null;
         }
 
-        try (final RandomAccessFile ras =
-                     new 
RandomAccessFile(JavaSecurityManagers.getSystemProperty("java.io.tmpdir", 
File.separator + "tmp") +
-                             File.separator + "passivation" + pointer.fileid + 
".ser", "r")) {
+        try (final RandomAccessFile ras = new 
RandomAccessFile(passivationFile(pointer.fileid), "r")) {
             final byte[] bytes = new byte[pointer.bytesize];
             ras.seek(pointer.filepointer);
             ras.readFully(bytes);
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java
new file mode 100644
index 0000000000..663a0a4277
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/core/managed/RAFPassivaterTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.core.managed;
+
+import org.apache.openejb.SystemException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+public class RAFPassivaterTest {
+
+    private static final byte[] PLANTED = {1, 2, 3};
+
+    @Rule
+    public final TemporaryFolder folder = new TemporaryFolder();
+
+    private String originalTmpDir;
+    private Path tmp;
+
+    @Before
+    public void useOwnTmpDir() throws IOException {
+        originalTmpDir = System.getProperty("java.io.tmpdir");
+        tmp = folder.newFolder("tmp").toPath();
+        System.setProperty("java.io.tmpdir", tmp.toString());
+    }
+
+    @After
+    public void restoreTmpDir() {
+        System.setProperty("java.io.tmpdir", originalTmpDir);
+    }
+
+    @Test
+    public void activateReturnsPassivatedState() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+
+        final Map<Object, Object> first = new HashMap<>();
+        first.put("a", "x");
+        first.put("b", new ArrayList<>(List.of("some", "longer", "state")));
+        first.put("c", 42);
+        passivater.passivate(first);
+
+        final Map<Object, Object> second = new HashMap<>();
+        second.put("d", "y".repeat(1000));
+        second.put("e", new HashMap<>(Map.of("k", "v")));
+        passivater.passivate(second);
+
+        final Map<Object, Object> all = new HashMap<>(first);
+        all.putAll(second);
+        for (final Map.Entry<Object, Object> entry : all.entrySet()) {
+            assertEquals(entry.getValue(), 
passivater.activate(entry.getKey()));
+        }
+        assertNull(passivater.activate("unknown"));
+    }
+
+    @Test
+    public void passivatesIntoPrivateDirectory() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+        passivater.passivate(state("b"));
+
+        final Path directory = privateDirectory();
+        assertTrue(Files.isRegularFile(directory.resolve("passivation0.ser")));
+        assertTrue(Files.isRegularFile(directory.resolve("passivation1.ser")));
+        if 
(directory.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+            assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE),
+                Files.getPosixFilePermissions(directory));
+        }
+    }
+
+    @Test
+    public void ignoresFilesPlantedInTmpDir() throws Exception {
+        final Path planted = Files.write(tmp.resolve("passivation0.ser"), 
PLANTED);
+        final Path target = Files.write(tmp.resolve("target"), PLANTED);
+        final boolean linked = link(tmp.resolve("passivation1.ser"), target);
+
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+        passivater.passivate(state("b"));
+
+        assertEquals("a", passivater.activate("a"));
+        assertEquals("b", passivater.activate("b"));
+        assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+        if (linked) {
+            assertArrayEquals(PLANTED, Files.readAllBytes(target));
+        }
+    }
+
+    @Test
+    public void refusesExistingFile() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+
+        final Path planted = 
Files.write(privateDirectory().resolve("passivation1.ser"), PLANTED);
+        assertRefused(passivater);
+        assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+    }
+
+    @Test
+    public void refusesLink() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+
+        final Path target = Files.write(tmp.resolve("target"), PLANTED);
+        assumeTrue("symbolic links not supported", 
link(privateDirectory().resolve("passivation1.ser"), target));
+        assertRefused(passivater);
+        assertArrayEquals(PLANTED, Files.readAllBytes(target));
+    }
+
+    private Path privateDirectory() throws IOException {
+        try (final Stream<Path> entries = Files.list(tmp)) {
+            final List<Path> directories = 
entries.filter(Files::isDirectory).toList();
+            assertEquals("expected a single passivation directory in " + tmp, 
1, directories.size());
+            return directories.get(0);
+        }
+    }
+
+    private static void assertRefused(final RAFPassivater passivater) {
+        try {
+            passivater.passivate(state("b"));
+            fail("an existing passivation file must not be reused");
+        } catch (final SystemException expected) {
+            // ok
+        }
+    }
+
+    private static boolean link(final Path link, final Path target) {
+        try {
+            Files.createSymbolicLink(link, target);
+            return true;
+        } catch (final UnsupportedOperationException | IOException e) {
+            return false;
+        }
+    }
+
+    private static Map<Object, Object> state(final String value) {
+        final Map<Object, Object> state = new HashMap<>();
+        state.put(value, value);
+        return state;
+    }
+}
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java
new file mode 100644
index 0000000000..896adfd8b2
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/core/stateful/RAFPassivaterTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.core.stateful;
+
+import org.apache.openejb.SystemException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+public class RAFPassivaterTest {
+
+    private static final byte[] PLANTED = {1, 2, 3};
+
+    @Rule
+    public final TemporaryFolder folder = new TemporaryFolder();
+
+    private String originalTmpDir;
+    private Path tmp;
+
+    @Before
+    public void useOwnTmpDir() throws IOException {
+        originalTmpDir = System.getProperty("java.io.tmpdir");
+        tmp = folder.newFolder("tmp").toPath();
+        System.setProperty("java.io.tmpdir", tmp.toString());
+    }
+
+    @After
+    public void restoreTmpDir() {
+        System.setProperty("java.io.tmpdir", originalTmpDir);
+    }
+
+    @Test
+    public void activateReturnsPassivatedState() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+
+        final Map<Object, Object> first = new HashMap<>();
+        first.put("a", "x");
+        first.put("b", new ArrayList<>(List.of("some", "longer", "state")));
+        first.put("c", 42);
+        passivater.passivate(first);
+
+        final Map<Object, Object> second = new HashMap<>();
+        second.put("d", "y".repeat(1000));
+        second.put("e", new HashMap<>(Map.of("k", "v")));
+        passivater.passivate(second);
+
+        final Map<Object, Object> all = new HashMap<>(first);
+        all.putAll(second);
+        for (final Map.Entry<Object, Object> entry : all.entrySet()) {
+            assertEquals(entry.getValue(), 
passivater.activate(entry.getKey()));
+        }
+        assertNull(passivater.activate("unknown"));
+    }
+
+    @Test
+    public void passivatesIntoPrivateDirectory() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+        passivater.passivate(state("b"));
+
+        final Path directory = privateDirectory();
+        assertTrue(Files.isRegularFile(directory.resolve("passivation0.ser")));
+        assertTrue(Files.isRegularFile(directory.resolve("passivation1.ser")));
+        if 
(directory.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+            assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, 
PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE),
+                Files.getPosixFilePermissions(directory));
+        }
+    }
+
+    @Test
+    public void ignoresFilesPlantedInTmpDir() throws Exception {
+        final Path planted = Files.write(tmp.resolve("passivation0.ser"), 
PLANTED);
+        final Path target = Files.write(tmp.resolve("target"), PLANTED);
+        final boolean linked = link(tmp.resolve("passivation1.ser"), target);
+
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+        passivater.passivate(state("b"));
+
+        assertEquals("a", passivater.activate("a"));
+        assertEquals("b", passivater.activate("b"));
+        assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+        if (linked) {
+            assertArrayEquals(PLANTED, Files.readAllBytes(target));
+        }
+    }
+
+    @Test
+    public void refusesExistingFile() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+
+        final Path planted = 
Files.write(privateDirectory().resolve("passivation1.ser"), PLANTED);
+        assertRefused(passivater);
+        assertArrayEquals(PLANTED, Files.readAllBytes(planted));
+    }
+
+    @Test
+    public void refusesLink() throws Exception {
+        final RAFPassivater passivater = new RAFPassivater();
+        passivater.passivate(state("a"));
+
+        final Path target = Files.write(tmp.resolve("target"), PLANTED);
+        assumeTrue("symbolic links not supported", 
link(privateDirectory().resolve("passivation1.ser"), target));
+        assertRefused(passivater);
+        assertArrayEquals(PLANTED, Files.readAllBytes(target));
+    }
+
+    private Path privateDirectory() throws IOException {
+        try (final Stream<Path> entries = Files.list(tmp)) {
+            final List<Path> directories = 
entries.filter(Files::isDirectory).toList();
+            assertEquals("expected a single passivation directory in " + tmp, 
1, directories.size());
+            return directories.get(0);
+        }
+    }
+
+    private static void assertRefused(final RAFPassivater passivater) {
+        try {
+            passivater.passivate(state("b"));
+            fail("an existing passivation file must not be reused");
+        } catch (final SystemException expected) {
+            // ok
+        }
+    }
+
+    private static boolean link(final Path link, final Path target) {
+        try {
+            Files.createSymbolicLink(link, target);
+            return true;
+        } catch (final UnsupportedOperationException | IOException e) {
+            return false;
+        }
+    }
+
+    private static Map<Object, Object> state(final String value) {
+        final Map<Object, Object> state = new HashMap<>();
+        state.put(value, value);
+        return state;
+    }
+}

Reply via email to