Running Tomcat on a privileged port used to be as simple as modifying the connector in Tomcat’s server.xml file. However, that meant running Tomcat as root — leaving open the possibility of privilege escalation and system compromise should exploitable vulnerabilities exist. As of Tomcat 6.0.24, the Debian/Ubuntu package includes some changes in the way Tomcat starts, including how it binds to privileged ports.
The installation of the tomcat6 package also creates the tomcat6 user and group. The user runs tomcat, and both the user and group own portions of the CATALINA_BASE directory tree. This is an unprivileged user, and so it cannot bind to privileged (aka well-known) ports. Many daemons start as root, bind to a privileged port and perform other setup work, then drop privileges and run as another user. Tomcat 6, however, now uses authbind(1) for this purpose. Authbind provides access control as follows, from the authbind(1) manpage:
Access to low numbered ports is controlled by permissions and contents of files in a configuration area, /etc/authbind.
Firstly, /etc/authbind/byport/port is tested. If this file is accessible for execution to the calling user, according to access(2), then binding to the port is authorised. If the file can be seen not to exist (the existence check returns ENOENT) then further tests will be used to find authorisation; otherwise, binding is not authorised, and the bind call will return with the errno value from the access(2) call, usually EACCES (Permission denied).
Secondly, if that test fails to resolve the matter, /etc/authbind/byaddr/addr:port is tested, in the same manner as above.
Thirdly, if the question is still unresolved, the file /etc/authbind/byuid/uid will be opened and read. If the file does not exist then the binding is not authorised and bind will return EPERM (Operation not permitted, or Not owner). If the file does exist it will be searched for a line of the form addr/length:min-port,max-port matching the request (ie, the initial length bits of addr match those in the proposed bind call, and the proposed port number lies is in the inclusive range specified. If such a line is found then the binding is authorised. Otherwise it is not, and bind will fail with ENOENT (No such file or directory).
In our case, the tomcat6 package creates a small file in /etc/authbind/byuid named with the UID of the tomcat6 user, and containing the line:
This allows the tomcat6 user to bind to any IP address with any low-numbered port, TCP or UDP.
So why does an attempt to start after having modified /etc/tomcat6/server.xml to start on TCP/80 fail with an error like the following?
SEVERE: Error starting endpoint java.net.BindException: Permission denied <null>:80
One piece is still missing! Authbind is not enabled by default. You will find the following section in the /etc/default/tomcat6 file:
# If you run Tomcat on port numbers that are all higher than 1023, then you # do not need authbind. It is used for binding Tomcat to lower port numbers. # NOTE: authbind works only with IPv4. Do not enable it when using IPv6. # (yes/no, default: no) #AUTHBIND=no
Uncomment the last line, and change ‘no’ to ‘yes’ and tomcat6 will start as you expect! Kudos to the package maintainers for being security conscious, although I’ll admit finding all of the clues to make this work was a bit of a chore.