Index: bin/catalina.sh =================================================================== --- bin/catalina.sh (revision 1060345) +++ bin/catalina.sh (working copy) @@ -276,6 +276,7 @@ -sourcepath "$CATALINA_HOME"/../../java \ -Djava.security.manager \ -Djava.security.policy=="$CATALINA_BASE"/conf/catalina.policy \ + -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask` \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ @@ -284,6 +285,7 @@ exec "$_RUNJDB" "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="$JAVA_ENDORSED_DIRS" -classpath "$CLASSPATH" \ -sourcepath "$CATALINA_HOME"/../../java \ + -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask` \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ @@ -303,6 +305,7 @@ -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \ -Djava.security.manager \ -Djava.security.policy==\"$CATALINA_BASE/conf/catalina.policy\" \ + -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask` \ -Dcatalina.base=\"$CATALINA_BASE\" \ -Dcatalina.home=\"$CATALINA_HOME\" \ -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \ @@ -310,6 +313,7 @@ else eval \"$_RUNJAVA\" \"$LOGGING_CONFIG\" $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs=\"$JAVA_ENDORSED_DIRS\" -classpath \"$CLASSPATH\" \ + -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask` \ -Dcatalina.base=\"$CATALINA_BASE\" \ -Dcatalina.home=\"$CATALINA_HOME\" \ -Djava.io.tmpdir=\"$CATALINA_TMPDIR\" \ Index: conf/server.xml =================================================================== --- conf/server.xml (revision 1060345) +++ conf/server.xml (working copy) @@ -21,6 +21,7 @@ --> + Index: java/org/apache/catalina/security/Constants.java =================================================================== --- java/org/apache/catalina/security/Constants.java (revision 0) +++ java/org/apache/catalina/security/Constants.java (revision 0) @@ -0,0 +1,25 @@ +/* + * 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.catalina.security; + +public class Constants { + + public static final String PACKAGE = "org.apache.catalina.security"; + + public static final String LINE_SEP = System.getProperty("line.separator"); + public static final String CRLF = "\r\n"; +} Property changes on: java\org\apache\catalina\security\Constants.java ___________________________________________________________________ Added: svn:eol-style + native Index: java/org/apache/catalina/security/LocalStrings.properties =================================================================== --- java/org/apache/catalina/security/LocalStrings.properties (revision 1060345) +++ java/org/apache/catalina/security/LocalStrings.properties (working copy) @@ -14,4 +14,7 @@ # limitations under the License. SecurityUtil.doAsPrivilege=An exception occurs when running the PrivilegedExceptionAction block. - +SecurityListener.checkUmaskFail=Start attempted with umask setting of [{0}]. Running Tomcat without a umask at least as restrictive as [{}] has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml) +SecurityListener.checkUmaskNone=No umask setting was found in system property [{0}]. However, it appears Tomcat is running on a platform that supports umask. The system property is typically set in CATALINA_HOME/bin/catalina.sh. The Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml) expects a umask at least as restrictive as [{1}] +SecurityListener.checkUmaskSkip=Unable to determine umask. It appears Tomcat is running on Windows so skip the umask check. +SecurityListener.checkUserWarning=Start attempted while running as user [{0}]. Running Tomcat as this user has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml) \ No newline at end of file Index: java/org/apache/catalina/security/SecurityListener.java =================================================================== --- java/org/apache/catalina/security/SecurityListener.java (revision 0) +++ java/org/apache/catalina/security/SecurityListener.java (revision 0) @@ -0,0 +1,186 @@ +/* + * 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.catalina.security; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; + +public class SecurityListener implements LifecycleListener { + + private static final Log log = LogFactory.getLog(SecurityListener.class); + + private static final StringManager sm = + StringManager.getManager(Constants.PACKAGE); + + private static final String UMASK_PROPERTY_NAME = + Constants.PACKAGE + ".SecurityListener.UMASK"; + + private static final String UMASK_FORMAT = "%04o"; + + /** + * The list of operating system users not permitted to run Tomcat. + */ + private Set checkedOsUsers = new HashSet(); + + /** + * The minimum umask that must be configured for the operating system user + * running Tomcat. The umask is handled as an octal. + */ + private Integer minimumUmask = Integer.valueOf(7); + + + public SecurityListener() { + checkedOsUsers.add("root"); + } + + + @Override + public void lifecycleEvent(LifecycleEvent event) { + // This is the earliest event in Lifecycle + if (event.getType().equals(Lifecycle.BEFORE_INIT_EVENT)) { + doChecks(); + } + } + + + /** + * Set the list of operating system users not permitted to run Tomcat. By + * default, only root is prevented from running Tomcat. Calling this method + * with null or the empty string will clear the list of users and + * effectively disables this check. User names will always be checked in a + * case insensitive manner. + * + * @param userList A comma separated list of operating system users not + * permitted to run Tomcat + */ + public void setCheckedOsUsers(String userNameList) { + if (userNameList == null || userNameList.length() == 0) { + checkedOsUsers.clear(); + } else { + String[] userNames = userNameList.split(","); + for (String userName : userNames) { + if (userName.length() > 0) { + checkedOsUsers.add(userName); + } + } + } + } + + + /** + * Returns the current list of operating system users not permitted to run + * Tomcat. + * + * @return A comma separated list of operating sytem user names. + */ + public String getCheckedOsUsers() { + if (checkedOsUsers.size() == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(); + Iterator iter = checkedOsUsers.iterator(); + result.append(iter.next()); + while (iter.hasNext()) { + result.append(','); + result.append(iter.next()); + } + return result.toString(); + } + + + /** + * Set the minimum umask that must be configured before Tomcat will start. + * + * @param umask The 4-digit umask as returned by the OS command umask + */ + public void setMinimumUmask(String umask) { + if (umask == null || umask.length() == 0) { + minimumUmask = Integer.valueOf(0); + } else { + minimumUmask = Integer.valueOf(umask, 8); + } + } + + + /** + * Get the minimum umask that must be configured before Tomcat will start. + * + * @return The 4-digit umask as used by the OS command umask + */ + public String getMinimumUmask() { + return String.format(UMASK_FORMAT, minimumUmask); + } + + + /** + * Execute the security checks. Each check should be in a separate method. + */ + protected void doChecks() { + checkOsUser(); + checkUmask(); + } + + + protected void checkOsUser() { + String userName = System.getProperty("user.name"); + if (userName != null) { + String userNameLC = userName.toLowerCase(); + + if (checkedOsUsers.contains(userNameLC)) { + // Have to throw Error to force start process to be aborted + throw new Error(sm.getString( + "SecurityListener.checkUserWarning", userName)); + } + } + } + + + protected void checkUmask() { + Integer umask = Integer.getInteger(UMASK_PROPERTY_NAME, null); + if (umask == null) { + if (Constants.CRLF.equals(Constants.LINE_SEP)) { + // Probably running on Windows so no umask + if (log.isDebugEnabled()) { + log.debug(sm.getString("SecurityListener.checkUmaskSkip")); + } + return; + } else { + if (minimumUmask.intValue() > 0) { + log.warn(sm.getString( + "SecurityListener.checkUmaskNone", + UMASK_PROPERTY_NAME, getMinimumUmask())); + } + return; + } + } + + if ((umask.intValue() & minimumUmask.intValue()) != + minimumUmask.intValue()) { + throw new Error(sm.getString("SecurityListener.checkUmaskFail", + String.format(UMASK_FORMAT, umask), getMinimumUmask())); + } + } +} Property changes on: java\org\apache\catalina\security\SecurityListener.java ___________________________________________________________________ Added: svn:eol-style + native Index: java/org/apache/catalina/security/SecurityUtil.java =================================================================== --- java/org/apache/catalina/security/SecurityUtil.java (revision 1060345) +++ java/org/apache/catalina/security/SecurityUtil.java (working copy) @@ -73,8 +73,6 @@ private static final org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( SecurityUtil.class ); - private static String PACKAGE = "org.apache.catalina.security"; - private static boolean packageDefinitionEnabled = (System.getProperty("package.definition") == null && System.getProperty("package.access") == null) ? false : true; @@ -83,7 +81,7 @@ * The string resources for this package. */ private static final StringManager sm = - StringManager.getManager(PACKAGE); + StringManager.getManager(Constants.PACKAGE); /** Index: res/confinstall/server_1.xml =================================================================== --- res/confinstall/server_1.xml (revision 1060345) +++ res/confinstall/server_1.xml (working copy) @@ -21,6 +21,7 @@ --> + Index: webapps/docs/changelog.xml =================================================================== --- webapps/docs/changelog.xml (revision 1060627) +++ webapps/docs/changelog.xml (working copy) @@ -54,6 +54,13 @@ length names being provided for users, roles and groups in the MemoryRealm and UserDatabaseRealm. (markt) + + 22405: Add a new Lifecycle listener, + org.apache.catalina.security.SecurityListener that, by + default, prevents Tomcat from starting insecurely. It requires that + Tomcat is not started as root and that a umask at least as restrictive + as 0007 is used. (markt) + Improve fix for 50205 to trigger an error earlier if invalid configuration is used. (markt) Index: webapps/docs/setup.xml =================================================================== --- webapps/docs/setup.xml (revision 1060345) +++ webapps/docs/setup.xml (working copy) @@ -131,8 +131,12 @@

jsvc has other useful parameters, such as -user which causes it to switch to another user after the daemon initialization is complete. This allows, for example, running Tomcat as a non privileged - user while still being able to use privileged ports. - jsvc --help will return the full jsvc usage + user while still being able to use privileged ports. Note that if you + use this option and start Tomcat as root, you'll need to disable the + org.apache.catalina.security.SecurityListener check that + prevents Tomcat starting when running as root.

+ +

jsvc --help will return the full jsvc usage information. In particular, the -debug option is useful to debug issues running jsvc.

Index: webapps/docs/config/listeners.xml =================================================================== --- webapps/docs/config/listeners.xml (revision 1060345) +++ webapps/docs/config/listeners.xml (working copy) @@ -333,6 +333,36 @@ +

Security Lifecycle Listener (org.apache.catalina.security.SecurityListener)

+ +

The Security Lifecycle Listener performs a number of + security checks when Tomcat starts and prevents Tomcat from starting if they + fail.

+ +

This listener must only be nested within Server + elements.

+ +

The following additional attributes are supported by the Security + Lifecycle Listener:

+ + + + +

A comma separated list of OS users that must not be used to start + Tomcat. If not specified, the default value of root is used. To + disable this check, set the attribute to the empty string. Usernames + are checked in a case-insensitive manner.

+
+ + +

The least rectrictive umask that must be configured before Tomcat + will start. If not specified, the default value of 0007 is used. + To disable this check, set the attribute to the empty string. The check + is not performed on Windows platforms.

+
+ +
+