Skip to content

Commit ad6c19e

Browse files
author
Maruan Sahyoun
committed
PDFBOX-6185: move temp directory creation to IOUtils; create only when needed
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1932684 13f79535-47bb-0310-9956-ffa450edef68
1 parent 6eb4a36 commit ad6c19e

2 files changed

Lines changed: 105 additions & 86 deletions

File tree

  • debugger/src/main/java/org/apache/pdfbox/debugger/ui
  • io/src/main/java/org/apache/pdfbox/io

debugger/src/main/java/org/apache/pdfbox/debugger/ui/Tree.java

Lines changed: 8 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.pdfbox.cos.COSName;
2323
import org.apache.pdfbox.cos.COSStream;
2424
import org.apache.pdfbox.debugger.treestatus.TreeStatus;
25+
import org.apache.pdfbox.io.IOUtils;
2526
import org.apache.pdfbox.pdmodel.common.PDStream;
2627

2728
import javax.swing.JMenuItem;
@@ -39,22 +40,12 @@
3940
import java.io.File;
4041
import java.io.IOException;
4142
import java.io.InputStream;
42-
import java.nio.file.FileSystems;
4343
import java.nio.file.Files;
4444
import java.nio.file.Path;
4545
import java.nio.file.StandardCopyOption;
46-
import java.nio.file.attribute.AclEntry;
47-
import java.nio.file.attribute.AclEntryPermission;
48-
import java.nio.file.attribute.AclEntryType;
49-
import java.nio.file.attribute.AclFileAttributeView;
50-
import java.nio.file.attribute.PosixFilePermissions;
51-
import java.nio.file.attribute.UserPrincipal;
5246
import java.util.ArrayList;
53-
import java.util.Collections;
54-
import java.util.Comparator;
5547
import java.util.List;
5648
import java.util.StringJoiner;
57-
import java.util.stream.Stream;
5849

5950
import org.apache.pdfbox.debugger.PDFDebugger;
6051

@@ -73,81 +64,8 @@ public class Tree extends JTree
7364
// Temporary files are stored in a private temp directory with restricted permissions,
7465
// which is deleted on exit using a shutdown hook.
7566
// PDFBOX-6185
76-
private static final Path TEMP_DIR = createTempDir();
77-
78-
private static Path createTempDir()
79-
{
80-
try
81-
{
82-
Path tempDir = Files.createTempDirectory("pdfbox-");
83-
applyOwnerOnlyPermissions(tempDir);
84-
85-
// use shutdown hook instead of deleteOnExit() to ensure deletion
86-
// of the entire directory in case of not automatically deleted on
87-
// JVM exit (e.g. due to hard crash or kill -9)
88-
Runtime.getRuntime().addShutdownHook(new Thread(() ->
89-
{
90-
try (Stream<Path> entries = Files.walk(tempDir))
91-
{
92-
entries.sorted(Comparator.reverseOrder())
93-
.forEach(p -> p.toFile().delete());
94-
}
95-
catch (IOException ignored) {}
96-
}));
97-
98-
return tempDir;
99-
}
100-
catch (IOException e)
101-
{
102-
throw new RuntimeException("Failed to create temporary directory for PDFDebugger", e);
103-
}
104-
}
105-
106-
private static void applyOwnerOnlyPermissions(Path dir) throws IOException
107-
{
108-
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
109-
{
110-
// Unix/macOS — rwx------
111-
Files.setPosixFilePermissions(dir, PosixFilePermissions.fromString("rwx------"));
112-
}
113-
else
114-
{
115-
// Windows — replace the entire ACL with a single owner-only ALLOW entry
116-
AclFileAttributeView aclView =
117-
Files.getFileAttributeView(dir, AclFileAttributeView.class);
118-
119-
if (aclView == null)
120-
{
121-
throw new IOException("Neither posix nor ACL view is supported on this file system");
122-
}
123-
124-
UserPrincipal owner = aclView.getOwner();
125-
126-
AclEntry ownerFullControl = AclEntry.newBuilder()
127-
.setType(AclEntryType.ALLOW)
128-
.setPrincipal(owner)
129-
.setPermissions(
130-
AclEntryPermission.READ_DATA,
131-
AclEntryPermission.WRITE_DATA,
132-
AclEntryPermission.APPEND_DATA,
133-
AclEntryPermission.READ_NAMED_ATTRS,
134-
AclEntryPermission.WRITE_NAMED_ATTRS,
135-
AclEntryPermission.EXECUTE,
136-
AclEntryPermission.DELETE_CHILD,
137-
AclEntryPermission.READ_ATTRIBUTES,
138-
AclEntryPermission.WRITE_ATTRIBUTES,
139-
AclEntryPermission.DELETE,
140-
AclEntryPermission.READ_ACL,
141-
AclEntryPermission.WRITE_ACL,
142-
AclEntryPermission.SYNCHRONIZE
143-
)
144-
.build();
145-
146-
// Set so that only the owner has permissions, and remove any inherited ACL entries
147-
aclView.setAcl(Collections.singletonList(ownerFullControl));
148-
}
149-
}
150-
67+
private Path tempDir;
68+
15169
/**
15270
* Constructor.
15371
*/
@@ -385,7 +303,11 @@ private JMenuItem getFileOpenMenu(final COSStream cosStream, final TreePath node
385303
{
386304
try
387305
{
388-
File temp = Files.createTempFile(TEMP_DIR, "pdfbox", "." + extension).toFile();
306+
if (tempDir == null)
307+
{
308+
tempDir = IOUtils.createProtectedTempDir();
309+
}
310+
File temp = Files.createTempFile(tempDir, "pdfbox", "." + extension).toFile();
389311

390312
try (InputStream is = cosStream.createInputStream())
391313
{

io/src/main/java/org/apache/pdfbox/io/IOUtils.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static java.util.Objects.nonNull;
2929

3030
import java.io.Closeable;
31+
import java.io.File;
3132
import java.io.IOException;
3233
import java.io.InputStream;
3334
import java.io.OutputStream;
@@ -37,11 +38,23 @@
3738
import java.lang.reflect.Field;
3839
import java.lang.reflect.Method;
3940
import java.nio.ByteBuffer;
41+
import java.nio.file.FileSystems;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
44+
import java.nio.file.attribute.AclEntry;
45+
import java.nio.file.attribute.AclEntryPermission;
46+
import java.nio.file.attribute.AclEntryType;
47+
import java.nio.file.attribute.AclFileAttributeView;
48+
import java.nio.file.attribute.PosixFilePermissions;
49+
import java.nio.file.attribute.UserPrincipal;
4050
import java.security.AccessController;
4151
import java.security.PrivilegedAction;
52+
import java.util.Collections;
53+
import java.util.Comparator;
4254
import java.util.Objects;
4355
import java.util.Optional;
4456
import java.util.function.Consumer;
57+
import java.util.stream.Stream;
4558

4659
import org.apache.logging.log4j.Logger;
4760
import org.apache.logging.log4j.LogManager;
@@ -319,4 +332,88 @@ public static StreamCacheCreateFunction createTempFileOnlyStreamCache()
319332
{
320333
return MemoryUsageSetting.setupTempFileOnly().streamCache;
321334
}
335+
336+
/**
337+
* Creates a temporary directory in the default temporary-file directory
338+
* with owner-only permissions and registers a shutdown hook to delete it on JVM exit.
339+
*
340+
* <p>Note: This method is designed to be used for storing temporary files that may contain sensitive data
341+
* in a temporary directories with restricted permissions, to mitigate the risk of unauthorized access by
342+
* other users or processes on the same system. Used e.g. by PDFDebugger.</p>
343+
*
344+
* @return the path to the created temporary directory
345+
* @throws IOException
346+
*/
347+
public static Path createProtectedTempDir() throws IOException
348+
{
349+
// S5443: permissions are immediately restricted to owner-only by
350+
// applyOwnerOnlyPermissions(), mitigating the default-permission risk.
351+
@SuppressWarnings("java:S5443")
352+
Path tempPath = Files.createTempDirectory("pdfbox-");
353+
applyOwnerOnlyPermissions(tempPath);
354+
355+
// use shutdown hook instead of deleteOnExit() to ensure deletion
356+
// of the entire directory in case of not automatically deleted on
357+
// JVM exit (e.g. due to open file handles or when the temp directory is not empty)
358+
Runtime.getRuntime().addShutdownHook(new Thread(() ->
359+
{
360+
try (Stream<Path> entries = Files.walk(tempPath))
361+
{
362+
entries.sorted(Comparator.reverseOrder())
363+
.forEach(p -> p.toFile().delete());
364+
}
365+
catch (IOException ignored) {}
366+
}));
367+
368+
return tempPath;
369+
}
370+
371+
private static void applyOwnerOnlyPermissions(Path dir) throws IOException
372+
{
373+
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
374+
{
375+
// Unix/macOS — rwx------
376+
Files.setPosixFilePermissions(dir, PosixFilePermissions.fromString("rwx------"));
377+
}
378+
else
379+
{
380+
// Windows — replace the entire ACL with a single owner-only ALLOW entry
381+
AclFileAttributeView aclView =
382+
Files.getFileAttributeView(dir, AclFileAttributeView.class);
383+
384+
if (aclView == null)
385+
{
386+
File tempDir = dir.toFile();
387+
tempDir.setReadable(true, true);
388+
tempDir.setWritable(true, true);
389+
tempDir.setExecutable(true, true);
390+
return;
391+
}
392+
393+
UserPrincipal owner = aclView.getOwner();
394+
395+
AclEntry ownerFullControl = AclEntry.newBuilder()
396+
.setType(AclEntryType.ALLOW)
397+
.setPrincipal(owner)
398+
.setPermissions(
399+
AclEntryPermission.READ_DATA,
400+
AclEntryPermission.WRITE_DATA,
401+
AclEntryPermission.APPEND_DATA,
402+
AclEntryPermission.READ_NAMED_ATTRS,
403+
AclEntryPermission.WRITE_NAMED_ATTRS,
404+
AclEntryPermission.EXECUTE,
405+
AclEntryPermission.DELETE_CHILD,
406+
AclEntryPermission.READ_ATTRIBUTES,
407+
AclEntryPermission.WRITE_ATTRIBUTES,
408+
AclEntryPermission.DELETE,
409+
AclEntryPermission.READ_ACL,
410+
AclEntryPermission.WRITE_ACL,
411+
AclEntryPermission.SYNCHRONIZE
412+
)
413+
.build();
414+
415+
// Set so that only the owner has permissions, and remove any inherited ACL entries
416+
aclView.setAcl(Collections.singletonList(ownerFullControl));
417+
}
418+
}
322419
}

0 commit comments

Comments
 (0)