This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 894158fd49 Improve security of Unix domain sockets - APR/Native
894158fd49 is described below
commit 894158fd49679a6bd48d9689905829e1fdc45cb2
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Aug 26 11:22:40 2026 +0100
Improve security of Unix domain sockets - APR/Native
---
java/org/apache/tomcat/util/net/AprEndpoint.java | 179 ++++++++++++++++++++++-
webapps/docs/changelog.xml | 7 +
webapps/docs/config/http.xml | 49 +++++++
3 files changed, 232 insertions(+), 3 deletions(-)
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java
b/java/org/apache/tomcat/util/net/AprEndpoint.java
index 2cad59753a..514ad78f6e 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -24,8 +24,10 @@ import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
@@ -310,6 +312,93 @@ public class AprEndpoint extends
AbstractEndpoint<Long,Long> implements SNICallB
}
+ /**
+ * Permissions which will be checked for / set on the parent directory of
the Unix Domain Socket path before the
+ * Unix Domain Socket is created.
+ */
+ private String unixDomainSocketParentPermissions = null;
+
+ /**
+ * Sets the permissions that will be checked for / set on the parent
directory of the Unix Domain Socket path
+ * before the Unix Domain Socket is created.
+ *
+ * @param unixDomainSocketParentPermissions the permissions string
+ */
+ public void setUnixDomainSocketParentPermissions(String
unixDomainSocketParentPermissions) {
+ this.unixDomainSocketParentPermissions =
unixDomainSocketParentPermissions;
+ }
+
+ /**
+ * Returns the permissions that will be checked for / set on the parent
directory of the Unix Domain Socket path
+ * before the Unix Domain Socket is created.
+ *
+ * @return the permissions string
+ */
+ public String getUnixDomainSocketParentPermissions() {
+ return unixDomainSocketParentPermissions;
+ }
+
+ private Set<PosixFilePermission>
getUnixDomainSocketParentPermissionsInternal() {
+ String permissionString = getUnixDomainSocketParentPermissions();
+ boolean derivePermissions = false;
+
+ if (permissionString == null) {
+ permissionString = getUnixDomainSocketPathPermissions();
+ /*
+ * UDS permissions will be file permissions which will be missing
the execute bit required at the directory
+ * level and may include write permissions which are not required
so set flag to derive a sensible default.
+ */
+ derivePermissions = true;
+ }
+
+ if (permissionString == null || permissionString.length() == 0) {
+ return null;
+ }
+
+ Set<PosixFilePermission> result =
PosixFilePermissions.fromString(permissionString);
+ if (derivePermissions) {
+ // Add execute before removing write
+ if (result.contains(PosixFilePermission.OWNER_READ) ||
result.contains(PosixFilePermission.OWNER_WRITE)) {
+ result.add(PosixFilePermission.OWNER_EXECUTE);
+ }
+ if (result.contains(PosixFilePermission.GROUP_READ) ||
result.contains(PosixFilePermission.GROUP_WRITE)) {
+ result.add(PosixFilePermission.GROUP_EXECUTE);
+ }
+ if (result.contains(PosixFilePermission.OTHERS_READ) ||
result.contains(PosixFilePermission.OTHERS_WRITE)) {
+ result.add(PosixFilePermission.OTHERS_EXECUTE);
+ }
+ result.remove(PosixFilePermission.GROUP_WRITE);
+ result.remove(PosixFilePermission.OTHERS_WRITE);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * If the parent directory for the UDS already exists, the name of the
expected owner.
+ */
+ private String unixDomainSocketParentOwner = null;
+
+ /**
+ * Returns the expected name of the owner of the parent directory for the
UDS if it already exists.
+ *
+ * @return the owner name
+ */
+ public String getUnixDomainSocketParentOwner() {
+ return unixDomainSocketParentOwner;
+ }
+
+ /**
+ * Sets the expected name of the owner of the parent directory for the UDS
if it already exists.
+ *
+ * @param unixDomainSocketParentOwner the expected owner name
+ */
+ public void setUnixDomainSocketParentOwner(String
unixDomainSocketParentOwner) {
+ this.unixDomainSocketParentOwner = unixDomainSocketParentOwner;
+ }
+
+
// --------------------------------------------------------- Public Methods
/**
@@ -380,6 +469,73 @@ public class AprEndpoint extends
AbstractEndpoint<Long,Long> implements SNICallB
} else {
throw new
Exception(sm.getString("endpoint.init.unixnotavail"));
}
+
+ /*
+ * If permissions are configured, need to create the parent folder
first with the correct permissions to
+ * avoid a TOCTOU issue with the Unix Domain Socket.
+ */
+ Set<PosixFilePermission> parentPosixPermissions =
getUnixDomainSocketParentPermissionsInternal();
+ if (parentPosixPermissions != null) {
+ Path udsPath =
Paths.get(getUnixDomainSocketPath()).toAbsolutePath();
+ Path parentPath = udsPath.getParent();
+ java.io.File parentFile = parentPath.toFile();
+
+ boolean posixSupported =
parentPath.getFileSystem().supportedFileAttributeViews().contains("posix");
+
+ if (!Files.exists(parentPath, LinkOption.NOFOLLOW_LINKS) ||
!Files.readAttributes(
+ parentPath, BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS).isDirectory()) {
+ if (posixSupported) {
+ /*
+ * The immediate parent will be configured with the
requested permissions. If other parents are
+ * created, they will be created with the specified
permissions masked as per the current
+ * process's UMASK.
+ */
+ Files.createDirectories(parentPath,
PosixFilePermissions.asFileAttribute(parentPosixPermissions));
+ FileAttribute<Set<PosixFilePermission>> attrs =
PosixFilePermissions.asFileAttribute(parentPosixPermissions);
+ Files.setAttribute(parentPath, attrs.name(),
attrs.value());
+ } else {
+ Files.createDirectories(parentPath);
+ }
+ }
+
+ // Directory should exist now
+ if (posixSupported) {
+ // Check permissions are at least as restrictive as those
specified.
+ Set<PosixFilePermission> currentPosixPermissions =
+ Files.getPosixFilePermissions(parentPath,
LinkOption.NOFOLLOW_LINKS);
+ currentPosixPermissions.removeAll(parentPosixPermissions);
+ if (!currentPosixPermissions.isEmpty()) {
+ throw new
IllegalStateException(sm.getString("endpoint.nio.uds.parentLaxPermissions",
+ parentPath, currentPosixPermissions));
+ }
+ } else {
+ log.warn(sm.getString("endpoint.nio.uds.parentNotPosix",
parentPath));
+ setPermissionsForNonPosixFile(parentFile,
parentPosixPermissions);
+ }
+
+ /*
+ * Check the owner - should either be the current user (if
created above) or the expected owner (if
+ * pre-created).
+ */
+ String expectedOwner = getUnixDomainSocketParentOwner();
+ if (expectedOwner == null || !expectedOwner.isEmpty()) {
+ String owner;
+ try {
+ owner = Files.getOwner(parentPath,
LinkOption.NOFOLLOW_LINKS).getName();
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ throw new
IllegalStateException(sm.getString("endpoint.nio.uds.noOwner", parentPath), t);
+ }
+ if (!owner.equals(expectedOwner)) {
+ String currentUser = System.getProperty("user.name");
+ if (!owner.equals(currentUser)) {
+ throw new
IllegalStateException(sm.getString("endpoint.nio.uds.parentOwner",
+ parentPath, owner, expectedOwner,
currentUser));
+
+ }
+ }
+ }
+ }
} else {
if (getAddress() != null) {
@@ -424,10 +580,17 @@ public class AprEndpoint extends
AbstractEndpoint<Long,Long> implements SNICallB
if (family == Socket.APR_UNIX) {
if (getUnixDomainSocketPathPermissions() != null) {
- FileAttribute<Set<PosixFilePermission>> attrs =
PosixFilePermissions
-
.asFileAttribute(PosixFilePermissions.fromString(getUnixDomainSocketPathPermissions()));
Path path = Paths.get(getUnixDomainSocketPath());
- Files.setAttribute(path, attrs.name(), attrs.value());
+ Set<PosixFilePermission> permissions =
+
PosixFilePermissions.fromString(getUnixDomainSocketPathPermissions());
+ if
(path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
+ FileAttribute<Set<PosixFilePermission>> attrs =
PosixFilePermissions.asFileAttribute(permissions);
+ Files.setAttribute(path, attrs.name(), attrs.value());
+ } else {
+ log.warn(sm.getString("endpoint.nio.uds.notPosix", path));
+ java.io.File file = path.toFile();
+ setPermissionsForNonPosixFile(file, permissions);
+ }
}
} else {
if (OS.IS_WIN32 || OS.IS_WIN64) {
@@ -476,6 +639,16 @@ public class AprEndpoint extends
AbstractEndpoint<Long,Long> implements SNICallB
}
+ private void setPermissionsForNonPosixFile(java.io.File file,
Set<PosixFilePermission> permissions) {
+ if (permissions.contains(PosixFilePermission.OTHERS_READ) &&
!file.setReadable(true, false)) {
+ log.warn(sm.getString("endpoint.nio.perms.readFail",
file.getAbsolutePath()));
+ }
+ if (permissions.contains(PosixFilePermission.OTHERS_WRITE) &&
!file.setWritable(true, false)) {
+ log.warn(sm.getString("endpoint.nio.perms.writeFail",
file.getAbsolutePath()));
+ }
+ }
+
+
@Override
protected void createSSLContext(SSLHostConfig sslHostConfig) throws
Exception {
OpenSSLContext sslContext = null;
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 90bb094137..97cca2b243 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -201,6 +201,13 @@
Align cipher order with the results produced by OpenSSL if the cipher
specification adds, removes and then re-adds ciphers. (markt)
</fix>
+ <add>
+ Add new attributes (<code>unixDomainSocketParentPermissions</code> and
+ <code>unixDomainSocketParentOwner</code>) to the APR/Native connector
to
+ provide additional control over the security of Unix Domain Sockets.
+ Additional checks (enabled by default) have also been added for the
+ directory where the Unix Domain Socket will be created.(markt)
+ </add>
</changelog>
</subsection>
<subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index bdcf7c994b..10b3fa54f2 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1270,6 +1270,55 @@
the response.</p>
</attribute>
+ <attribute name="unixDomainSocketPath" required="false">
+ <p>Where supported, the path to a Unix Domain Socket that this
+ <strong>Connector</strong> will create and await incoming connections.
+ When this is specified, the otherwise mandatory <code>port</code>
+ attribute may be omitted.</p>
+ </attribute>
+
+ <attribute name="unixDomainSocketPathPermissions" required="false">
+ <p>Where supported, the posix permissions that will be applied to the
+ Unix Domain Socket specified with <code>unixDomainSocketPath</code>
+ above. If the Unix Domain Socket is required to be created securely,
+ these permissions should be selected as to limit access to trusted
+ users.</p>
+ <p>The permissions are specified as a string of nine characters, in
+ three sets of three: (r)ead, (w)rite and e(x)ecute for owner, group and
+ others respectively. If a permission is not granted, a hyphen is
+ used.</p>
+ <p>If unspecified, no permissions are explicitly set meaning that the
+ resulting permissions will be determined by the current umask.</p>
+ </attribute>
+
+ <attribute name="unixDomainSocketParentOwner" required="false">
+ <p>The expected ownership of the directory where the Unix Domain Socket
+ will be created. The directory must be owned either by this user or the
+ current user associated with the Tomcat process.</p>
+ <p>This attribute is ignored if
+ <code>unixDomainSocketParentPermissions</code> are skipped. If not
+ ignored, the ownership checks on the parent directory, can be skipped
by
+ setting this attribute to the empty string.</p>
+ <p>If unspecified, the default value is unset.</p>
+ </attribute>
+
+ <attribute name="unixDomainSocketParentPermissions" required="false">
+ <p>Where supported, the posix permissions that will be checked on /
+ applied to the directory where the Unix Domain Socket will be created.
+ If the Unix Domain Socket is required to be created securely, these
+ permissions should be selected as to limit access to trusted users.</p>
+ <p>To skip the permission checks on the parent directory, set this
+ attribute to the empty string.</p>
+ <p>The permissions are specified as a string of nine characters, in
+ three sets of three: (r)ead, (w)rite and e(x)ecute for owner, group and
+ others respectively. If a permission is not granted, a hyphen is used.
+ If unspecified, the value of
+ <code>unixDomainSocketPathPermissions</code> is used with the addition
+ of the appropriate execute permission for every read and/or write
+ permission that is specified and the removal of group and other write
+ permissions if present.</p>
+ </attribute>
+
</attributes>
</subsection>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]