The BSD r* commands and the history of privileged TCP ports

July 11, 2017

Once upon a time, UCB was adding TCP/IP to (BSD) Unix. They had multiple Unix machines, and one obvious thing to want when you have networking and multiple Unix machines is a way to log in and transfer files from one machine to another. Fortunately for the UCB BSD developers, TCP/IP already had well-specified programs (and protocols) to do this, namely telnet and FTP. So all they had to do was implement telnet and FTP clients and servers and they were done, right?

The USB BSD people did implement telnet and FTP, but they weren't satisfied with just that, apparently because neither was convenient and flexible enough. In particular, telnet and FTP have baked into them the requirement for a password. Obviously you need to ask people to authenticate themselves (with a password) when you're accepting remote connections from who knows where, but the UCB people were just logging in and transferring files and so on between their own local collection of Vaxes. So the BSD people wound up creating a better way, in the form of the r* commands: rlogin, rsh, and rcp. The daemons that implemented these were rlogind and rshd.

(See also rcmd(3).)

The authentication method was pretty simple; it relied on checking /etc/hosts.equiv to see if the client host was trusted in general, or ~/.rhosts to see if you were allowing logins from that particular remote user on the remote host. As part of this, these daemons obviously relied on the client not lying about who the remote user was (and what their login name was). How did they have some assurance about this? The answer is that the BSD developers added a hack to their TCP/UDP implementation, namely a new idea of 'privileged ports'.

Privileged ports were ports under 1024 (aka IPPORT_RESERVED), and the hack was that the kernel only allowed them to be used by UID 0. If you asked for 'give me any port', they were skipped, and if you weren't root and tried to bind(2) to such a port, the kernel rejected you. Rlogind and rshd insisted that client connections come from privileged ports, at which point they knew that it was a root-owned process talking to them from the client and they could trust its claims of what the remote login name was. Ordinary users on the client couldn't make their own connection and claim to be someone else, because they wouldn't be allowed to use a privileged port.

(Sun later reused this port-based authentication mechanism as part of NFS security.)

Based on the Unix versions available on tuhs.org, all of this appears to have been introduced in 4.1c BSD. This is the version that adds (IPPORT_RESERVED)) to netinet/in.h and has the TCP/UDP port binding code check it in in_pcbbind in netinet/in_pcb.c. In case you think the BSD people thought that this was an elegant idea, let me show you the code:

if (lport) {
  u_short aport = htons(lport);
  int wild = 0;

  /* GROSS */
  if (aport < IPPORT_RESERVED && u.u_uid != 0)
    return (EACCES);
  [...]

The BSD people knew this was a hack; they just did it anyway, probably because it was a very handy hack in their trusted local network environment. Unix has quietly inherited it ever since.

(Privileged ports are often called 'reserved ports', as in 'reserved for root only'. Even the 4.1c BSD usage here is inconsistent; the actual #define is IPPORT_RESERVED, but things like the rlogind manpage talk about 'privileged port numbers'. Interestingly, in 4.1c BSD the source code for the r* programs is hanging out in an odd place, in /usr/src/ucb/netser, along with several other things. By the release of 4.2 BSD, they had all been relocated to /usr/src/ucb and /usr/src/etc, where you'd expect.)

PS: Explicitly using a privileged port when connecting to a server is one of the rare cases when you need to call bind() for an outgoing socket, which is usually something you want to avoid.

Sidebar: The BUGS section of rlogind and rshd is wise

In the style of Unix manpages admitting big issues that they don't have any good way of dealing with at the moment, the manpages of both rlogind and rshd end with:

.SH BUGS
The authentication procedure used here assumes the
integrity of each client machine and the connecting
medium.  This is insecure, but is useful in an ``open''
environment.

.PP
A facility to allow all data exchanges to be encrypted
should be present.

These issues would stay for a decade or so, getting slowly more significant over time, until they were finally fixed by SSH in 1995. Well, starting in 1995, the switch wasn't exactly instant and even now the process of replacing rsh and rlogin is sort of ongoing (and also).

Written on 11 July 2017.
« Ubuntu's 'Daily Build' images aren't for us
Recursive DNS servers send the whole original query to authoritative servers »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Jul 11 00:38:16 2017
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.