An important safety note about chown and symlinks (also chmod and chgrp)

April 20, 2020

Today I had a little Twitter exchange about a little (or not so little) thing that is not as well known as it should be:

@thatcks: Ah yes, the eternal question: is it 'chown -Hr' or 'chown -hR' that I want?

(Answer: the second. Also, -h should be the default, at least with -R.)

@mrhoten: TIL I have run some pretty dang risky chown -R's and not even known it.

For those who haven't encountered this particular combination of arguments for the chown command, -R is a recursive chown through a directory hierarchy, while -h is, well, let me quote the manual (for GNU chown):

affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)

(Similar wording is in the FreeBSD chown manpage.)

Let me translate this. If you run plain 'chown -R user.group dirtree', and the directory tree contains symlinks that point outside itself, your recursive chown will change the ownership of the files that those symlinks point to. Is there a symlink to /etc/shadow? Well, you just changed its ownership. A recursive chown without -h can do random damage to owners and groups anywhere on your system.

Recursive chowns aren't the only time this comes up. Consider 'chown user.group *' in a directory, to fix ownership for the user. If there are any symlinks, congratulations, you once again changed the ownership of random files on your system. You should always use -h with chown unless you're sure you know what you're doing. You almost never want to change the ownership of the target of a symlink.

(If you have a tangled chain of symlinks and you need to fix the permissions of the real underlying file behind them all, generally use realpath to find out what it is first.)

Naturally the same thing applies to the chgrp command. The GNU chmod command unfortunately lacks a -h option, and will always change permissions on the target of symlinks given on the command line (which might happen in, for example, 'chmod g+r *'). FreeBSD's chmod does have a -h option and you should always use it unless you're sure.

Sidebar: The potential bad state of OpenBSD and chown

The OpenBSD chown manpage contains the following hair-raising statement about the -h option (emphasis mine):

Treat symbolic links like other files: modify links instead of following them. The -h and -R options are mutually exclusive.

OpenBSD's chown also has a -P argument, which is the default behavior in 'chown -R'. This is described as:

If the -R option is specified, no symbolic links are followed.

It's not clear what OpenBSD's manpage means by 'followed' here. Are symbolic links that point to directories not descended into but symbolic links are chown'd in general, or are symbolic links entirely ignored, being neither chown'd nor descended into? If OpenBSD chown really does follow the POSIX chown specification, then the POSIX specification is much clearer here and specifies the behavior we don't want:

If the -R option is specified and a symbolic link is specified on the command line or encountered during the traversal of a file hierarchy, chown shall change the owner ID (and group ID, if specified) of the symbolic link. The chown utility shall not follow the symbolic link to any other part of the file hierarchy.

Update: I think I misread this and was wrong about the POSIX behavior, because it specifies that with -P, the symbolic link itself is chown'd. That would make it the behavior we want.

If OpenBSD chown really uses the POSIX behavior, then you can't safely use chown on OpenBSD to change the ownership of a directory tree. You need to use a much more awkward find command line to chown everything that isn't a symlink.

(POSIX appears to implicitly forbid combining '-h' and '-R', but that's crazy and may be a mistake in POSIX.)


Comments on this page:

A few years ago I found a bug in OpenBSD's chown where, without -h, it was reading the permissions from the symlink, mutating them according to the command, and applying them to the target. This came up in my dotfiles install script where I do want to follow symlinks when adjusting permissions.

You fear is not true for GNU coreutils, check chown(1):

      The following options modify how a hierarchy is traversed when the -R
      option is also specified.  If more than one is specified, only the
      final one takes effect.

      -H     if a command line argument is a symbolic link to a directory,
             traverse it

      -L     traverse every symbolic link to a directory encountered

      -P     do not traverse any symbolic links (default)

So -P is default, and nothing happens. (And the symlinks are chowned fine.)

By cks at 2020-04-21 18:48:05:

If the GNU chown manpage is intended to reassure us that 'chown -hR' is unnecessary, then this section is extremely badly worded for this purpose. It starts out by talking explicitly about how the (directory) hierarchy is traversed, ie descended through recursively, and the -P option repeats usage of 'traverse'. It is technically true that chown(2) traverses (resolves) the symlink when applied to a name that is a symlink and so you could say that this phrasing is technically accurate about not chown'ing the targets of symlinks, but at the minimum it is very unclear and alarming. If GNU chown means that -P does not chown symlinks as well as not descending through symlinks that point to directories, it should say so.

(This appears to be the actual behavior, but since it's not documented, who knows.)

POSIX is at least explicit here, although it's apparently still possible for me to misread it and initially mis-understand what POSIX means by saying that 'chown shall change the [...] of the symbolic link'.

Written on 20 April 2020.
« Verifying the server hostname for a TLS certificate has two purposes
More on chown in combination with symlinks »

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

Last modified: Mon Apr 20 23:40:21 2020
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.