SGID on Directories

Traditionally, as I remember, setting SGID on a directory makes the files which are created within to inherit the group of the directory.

For whatever reason, on my systems this is always the case, with or without SGID bit.
Nevertheless, the bit can be set, but then recursive cp(1) may fail in unexpected ways (without actually failing).

Code:
$ id
uid=1100(pmc) gid=20(staff) groups=20(staff),5(operator)
$ ls -ld /var/tmp
drwxrwxrwt  15 root  wheel  254 Apr 13 02:00 /var/tmp
$ mkdir /var/tmp/XXX
$ ls -ld /var/tmp/XXX
drwxr-xr-x  2 pmc  wheel  2 Apr 13 02:03 /var/tmp/XXX

Now the fancy part:

Code:
$ chgrp staff /var/tmp/XXX
$ chmod g+s /var/tmp/XXX
$ mkdir /var/tmp/XXX/YYY
$ ls -ld /var/tmp/XXX /var/tmp/XXX/YYY
drwxr-sr-x  3 pmc  staff  3 Apr 13 02:05 /var/tmp/XXX
drwxr-sr-x  2 pmc  staff  2 Apr 13 02:05 /var/tmp/XXX/YYY
$ cp -R /var/tmp/XXX /var/tmp/ZZZ
cp: chmod: /var/tmp/ZZZ/YYY: Operation not permitted
cp: chmod: /var/tmp/ZZZ: Operation not permitted
$ echo $?
1
$ ls -ld /var/tmp/ZZZ /var/tmp/ZZZ/YYY
drwxr-xr-x  3 pmc  wheel  3 Apr 13 02:07 /var/tmp/ZZZ
drwxr-xr-x  2 pmc  wheel  2 Apr 13 02:07 /var/tmp/ZZZ/YYY

We can see, cp has correctly copied the tree. According to the documentation, the -R flag copies directory permissions also. As the original directories have the SGID bit set, cp tries to set it on the destination also. But we cannot set an SGID bit with a group that we don't belong to, and the group of the new directories is wheel because that gets inherited without an SGID bit set.

I don't find any notice on the net about the directory SGID behaviour happening even without SGID, neither about a tunable knob where this can be switched. But if this was intentionally implemented in his way, the cp behaviour should also be changed to silently ignore these errors, instead of breaking build scripts.
 
Note that /var/tmp has the sticky(7) bit set.

Yes. That is important for directores writeable by multiple users.

My question is rather: what functional effect comes from setting the sgid bit on a directory? The effect that it would usually have (inherit the group to the files below) does already happen by default. And some papers recommend to set sticky, but NOT sgid on /tmp or /var/tmp.
 
chmod(1) and chmod(2) don't mention anything about the SGID doing anything special for directories, just executable files. open(2) specifically states "When a new file is created it is given the group of the directory which contains it", and mkdir(2) mentions the same behavior when it comes to creating new directories.

Working in those tmp directories with the sticky bit set, you would need to a couple more steps and change the recursive copy slightly:

Code:
mkdir /var/tmp/ZZZ
chgrp staff /var/tmp/ZZZ
cp -R /var/tmp/XXX/. /var/tmp/ZZZ/
 
chmod(1) and chmod(2) don't mention anything about the SGID doing anything special for directories, just executable files.

So the conclusion is: SGID is a noop on directories, except that it can break cp -R.
On most other flavors of unix (and probably on linux) this is different. (And I thought that was POSIX about to avoid. Well, never mind.)
Working in those tmp directories with the sticky bit set, you would need to a couple more steps and change the recursive copy slightly:

Code:
mkdir /var/tmp/ZZZ
chgrp staff /var/tmp/ZZZ
cp -R /var/tmp/XXX/. /var/tmp/ZZZ/

No, I need an interim directory anyway for read protection, that is not the problem. I was just used to add SGID to such directories, and the solution is to leave that away.

The actual task was to have a deploy done by admin, which should create a tree under /var/app, but should not overwrite the tree of the currently running app. Then during cutover, the application-user needs to bring this new tree into the actual place. The whole process should not need root permissions, and the directory should stay unreadable to other users.
My solution is to set /var/app to admin:wheel with 1777. Then the application users can create their directories below with 0770, and these will inherit wheel, so that admin can place the deploy into them, while they cannot be read by other app-users with their same app-group.
On FreeBSD this happens by default; on other systems one would need to take extra care that the directory gets group wheel and not the app-group.
 
So the conclusion is: SGID is a noop on directories, except that it can break cp -R.
On most other flavors of unix (and probably on linux) this is different. (And I thought that was POSIX about to avoid. Well, never mind.)
Does someone have half an hour to spare, and can look up the exact definition of SGID in the POSIX standard? There are two possibilities. Either POSIX specifies the exact behavior, in which case either *BSD or Linux is wrong. And if it is *BSD, we might want to consider opening a PR to fix it, since POSIX compliance is valuable. Or POSIX does not specify the behavior, in which case POSIX is less useful than it could be.
 
  • Like
Reactions: PMc
Haven't found the details from POSIX but I did find that BSD and SystemV have slightly different behaviors. Basically BSD always behaves as if setgid is set and SystemV based systems do not.

On BSD systems a new file will have group set to the group of the directory. On SystemV systems it will have the primary group of the user that created the file. In this respect Linux appears to follow the SystemV behavior by default (but can be switched to BSD behavior with a mount option or the setgid on a directory).


Code:
       O_CREAT
	      If  the file does	not exist it will be created.  The owner (user
	      ID) of the file is set to	the effective user ID of the  process.
	      The  group  ownership  (group ID)	is set either to the effective
	      group ID of the process or to the	group ID of the	parent	direc-
	      tory  (depending	on file	system type and	mount options, and the
	      mode of the parent directory, see	the  mount  options  bsdgroups
	      and sysvgroups described in mount(8)).
Excerpt from CentOS's open(2) man page.

Code:
     When a new	file is	created	it is given the	group of the directory which
     contains it.
FreeBSD's open(2) man page.

That was an interesting search, learned a lot more than I expected :D
 
  • Thanks
Reactions: PMc
I'm currently studying for my LPIC-1 and wanted to test the sticky bit, and the newgrp command to change the effective group ID of the user and its effect on creating files. Anyway, I was quite surprises when testing this on my BSD machine as I was also not aware that BSD by default gives the new file the group of the directory containing the file.
 
I came across this issue. The POSIX description of mkdir is interesting.

The directory's user ID shall be set to the process' effective user ID. The directory's group ID shall be set to the group ID of the parent directory or to the effective group ID of the process. Implementations shall provide a way to initialize the directory's group ID to the group ID of the parent directory. Implementations may, but need not, provide an implementation-defined way to initialize the directory's group ID to the effective group ID of the calling process.
There is some bias towards setting the group to that of the parent directory, but both FreeBSD and Linux function according to the specification.
 
Traditionally, as I remember, setting SGID on a directory makes the files which are created within to inherit the group of the directory.

For whatever reason, on my systems this is always the case, with or without SGID bit.
Nevertheless, the bit can be set, but then recursive cp(1) may fail in unexpected ways (without actually failing).

Code:
$ id
uid=1100(pmc) gid=20(staff) groups=20(staff),5(operator)
$ ls -ld /var/tmp
drwxrwxrwt  15 root  wheel  254 Apr 13 02:00 /var/tmp
$ mkdir /var/tmp/XXX
$ ls -ld /var/tmp/XXX
drwxr-xr-x  2 pmc  wheel  2 Apr 13 02:03 /var/tmp/XXX

Now the fancy part:

Code:
$ chgrp staff /var/tmp/XXX
$ chmod g+s /var/tmp/XXX
$ mkdir /var/tmp/XXX/YYY
$ ls -ld /var/tmp/XXX /var/tmp/XXX/YYY
drwxr-sr-x  3 pmc  staff  3 Apr 13 02:05 /var/tmp/XXX
drwxr-sr-x  2 pmc  staff  2 Apr 13 02:05 /var/tmp/XXX/YYY
$ cp -R /var/tmp/XXX /var/tmp/ZZZ
cp: chmod: /var/tmp/ZZZ/YYY: Operation not permitted
cp: chmod: /var/tmp/ZZZ: Operation not permitted
$ echo $?
1
$ ls -ld /var/tmp/ZZZ /var/tmp/ZZZ/YYY
drwxr-xr-x  3 pmc  wheel  3 Apr 13 02:07 /var/tmp/ZZZ
drwxr-xr-x  2 pmc  wheel  2 Apr 13 02:07 /var/tmp/ZZZ/YYY

We can see, cp has correctly copied the tree. According to the documentation, the -R flag copies directory permissions also. As the original directories have the SGID bit set, cp tries to set it on the destination also. But we cannot set an SGID bit with a group that we don't belong to, and the group of the new directories is wheel because that gets inherited without an SGID bit set.

I don't find any notice on the net about the directory SGID behaviour happening even without SGID, neither about a tunable knob where this can be switched. But if this was intentionally implemented in his way, the cp behaviour should also be changed to silently ignore these errors, instead of breaking build scripts.
The BSD's already do this.

This was prescribed by the FIPS-151 standard. And the USG only bought systems which conformed to FIPS.

Only BSD back in the day conformed to FIPS. Unfortunately SYSV did not. In order to be able to sell computers to the USG, SYSV added the setgid bit to directories to make SYSV behave like BSD, thus conforming to FIPS.

Linux does what SYSV did. Hence Linux needs to the setgid for directories to behave like BSD and conform to FIPS-151.

As a side note, IBM mainframe did not conform to FIPS. It wasn't UNIX. So IBM developed MVS Open Edition, an app that ran on the mainframe that conformed to FIPS, allowing the USG to continue purchasing IBM mainframes.

Short answer. Don't worry about propagation of group ownership. The BSDs do this by default. They only do this. They do not do what SYSV, and thus Linux, do.
 
Back
Top