Linux ignores the setuid¹ bit on all interpreted executables (i.e. executables starting with a #!
line). The comp.unix.questions FAQ explains the security problems with setuid shell scripts. These problems are of two kinds: shebang-related and shell-related; I go into more details below.
If you don't care about security and want to allow setuid scripts, under Linux, you'll need to patch the kernel. As of 3.x kernels, I think you need to add a call to install_exec_creds
in the load_script
function, before the call to open_exec
, but I haven't tested.
Setuid shebang
There is a race condition inherent to the way shebang (#!
) is typically implemented:
- The kernel opens the executable, and finds that it starts with
#!
.
- The kernel closes the executable and opens the interpreter instead.
- The kernel inserts the path to the script to the argument list (as
argv[1]
), and executes the interpreter.
If setuid scripts are allowed with this implementation, an attacker can invoke an arbitrary script by creating a symbolic link to an existing setuid script, executing it, and arranging to change the link after the kernel has performed step 1 and before the interpreter gets around to opening its first argument. For this reason, most unices ignore the setuid bit when they detect a shebang.
One way to secure this implementation would be for the kernel to lock the script file until the interpreter has opened it (note that this must prevent not only unlinking or overwriting the file, but also renaming any directory in the path). But unix systems tend to shy away from mandatory locks, and symbolic links would make a correct lock feature especially difficult and invasive. I don't think anyone does it this way.
A few unix systems (mainly OpenBSD, NetBSD and Mac OS X, all of which require a kernel setting to be enabled) implement secure setuid shebang using an additional feature: the path /dev/fd/N
refers to the file already opened on file descriptor N (so opening /dev/fd/N
is roughly equivalent to dup(N)
). Many unix systems (including Linux) have /dev/fd
but not setuid scripts.
- The kernel opens the executable, and finds that it starts with
#!
. Let's say the file descriptor for the executable is 3.
- The kernel opens the interpreter.
- The kernel inserts
/dev/fd/3
the argument list (as argv[1]
), and executes the interpreter.
Sven Mascheck's shebang page has a lot of information on shebang across unices, including setuid support.
Setuid interpreters
Let's assume you've managed to make your program run as root, either because your OS supports setuid shebang or because you've used a native binary wrapper (such as sudo
). Have you opened a security hole? Maybe. The issue here is not about interpreted vs compiled programs. The issue is whether your runtime system behaves safely if executed with privileges.
Any dynamically linked native binary executable is in a way interpreted by the dynamic loader (e.g. /lib/ld.so
), which loads the dynamic libraries required by the program. On many unices, you can configure the search path for dynamic libraries through the environment (LD_LIBRARY_PATH
is a common name for the environment variable), and even load additional libraries into all executed binaries (LD_PRELOAD
). The invoker of the program can execute arbitrary code in that program's context by placing a specially-crafted libc.so
in $LD_LIBRARY_PATH
(amongst other tactics). All sane systems ignore the LD_*
variables in setuid executables.
In shells such as sh, csh and derivatives, environment variables automatically become shell parameters. Through parameters such as PATH
, IFS
, and many more, the invoker of the script has many opportunities to execute arbitrary code in the shell scripts's context. Some shells set these variables to sane defaults if they detect that the script has been invoked with privileges, but I don't know that there is any particular implementation that I would trust.
Most runtime environments (whether native, bytecode or interpreted) have similar features. Few take special precautions in setuid executables, though the ones that run native code often don't do anything fancier than dynamic linking (which does take precautions).
Perl is a notable exception. It explicitly supports setuid scripts in a secure way. In fact, your script can run setuid even if your OS ignored the setuid bit on scripts. This is because perl ships with a setuid root helper that performs the necessary checks and reinvokes the interpreter on the desired scripts with the desired privileges. This is explained in the perlsec manual. It used to be that setuid perl scripts needed #!/usr/bin/suidperl -wT
instead of #!/usr/bin/perl -wT
, but on most modern systems, #!/usr/bin/perl -wT
is sufficient.
Note that using a native binary wrapper does nothing in itself to prevent these problems. In fact, it can make the situation worse, because it might prevent your runtime environment from detecting that it is invoked with privileges and bypassing its runtime configurability.
A native binary wrapper can make a shell script safe if the wrapper sanitizes the environment. The script must take care not to make too many assumptions (e.g. about the current directory) but this goes. You can use sudo for this provided that it's set up to sanitize the environment. Blacklisting variables is error-prone, so always whitelist. With sudo, make sure that the env_reset
option is turned on, that setenv
is off, and that env_file
and env_keep
only contain innocuous variables.
TL,DR:
- Setuid shebang is insecure but usually ignored.
- If you run a program with privileges (either through sudo or setuid), write native code or perl, or start the program with a wrapper that sanitizes the environment (such as sudo with the
env_reset
option).
¹ This discussion applies equally if you substitute “setgid” for “setuid”; they are both ignored by the Linux kernel on scripts
I’ll answer your questions in three parts: file types, permissions, and use cases for the various forms of chmod
.
File types
The first character in ls -l
output represents the file type; d
means it’s a directory. It can’t be set or unset, it depends on how the file was created. You can find the complete list of file types in the ls documentation; those you’re likely to come across are
-
: “regular” file, created with any program which can write a file
b
: block special file, typically disk or partition devices, can be created with mknod
c
: character special file, can also be created with mknod
(see /dev
for examples)
d
: directory, can be created with mkdir
l
: symbolic link, can be created with ln -s
p
: named pipe, can be created with mkfifo
s
: socket, can be created with nc -U
D
: door, created by some server processes on Solaris/openindiana.
Permissions
chmod 0777
is used to set all the permissions in one chmod
execution, rather than combining changes with u+
etc. Each of the four digits is an octal value representing a set of permissions:
suid
, sgid
and “sticky” (see below)
- user permissions
- group permissions
- “other” permissions
The octal value is calculated as the sum of the permissions:
- “read” is 4
- “write” is 2
- “execute” is 1
For the first digit:
suid
is 4; binaries with this bit set run as their owner user (commonly root
)
sgid
is 2; binaries with this bit set run as their owner group (this was used for games so high scores could be shared, but it’s often a security risk when combined with vulnerabilities in the games), and files created in directories with this bit set belong to the directory’s owner group by default (this is handy for creating shared folders)
- “sticky” (or “restricted deletion”) is 1; files in directories with this bit set can only be deleted by their owner, the directory’s owner, or
root
(see /tmp
for a common example of this).
See the chmod
manpage for details. Note that in all this I’m ignoring other security features which can alter users’ permissions on files (SELinux, file ACLs...).
Special bits are handled differently depending on the type of file (regular file or directory) and the underlying system. (This is mentioned in the chmod
manpage.) On the system I used to test this (with coreutils
8.23 on an ext4
filesystem, running Linux kernel 3.16.7-ckt2), the behaviour is as follows. For a file, the special bits are always cleared unless explicitly set, so chmod 0777
is equivalent to chmod 777
, and both commands clear the special bits and give everyone full permissions on the file. For a directory, the special bits are never fully cleared using the four-digit numeric form, so in effect chmod 0777
is also equivalent to chmod 777
but it’s misleading since some of the special bits will remain as-is. (A previous version of this answer got this wrong.) To clear special bits on directories you need to use u-s
, g-s
and/or o-t
explicitly or specify a negative numeric value, so chmod -7000
will clear all the special bits on a directory.
In ls -l
output, suid
, sgid
and “sticky” appear in place of the x
entry: suid
is s
or S
instead of the user’s x
, sgid
is s
or S
instead of the group’s x
, and “sticky” is t
or T
instead of others’ x
. A lower-case letter indicates that both the special bit and the executable bit are set; an upper-case letter indicates that only the special bit is set.
The various forms of chmod
Because of the behaviour described above, using the full four digits in chmod
can be confusing (at least it turns out I was confused). It’s useful when you want to set special bits as well as permission bits; otherwise the bits are cleared if you’re manipulating a file, preserved if you’re manipulating a directory. So chmod 2750
ensures you’ll get at least sgid
and exactly u=rwx,g=rx,o=
; but chmod 0750
won’t necessarily clear the special bits.
Using numeric modes instead of text commands ([ugo][=+-][rwxXst]
) is probably more a case of habit and the aim of the command. Once you’re used to using numeric modes, it’s often easier to just specify the full mode that way; and it’s useful to be able to think of permissions using numeric modes, since many other commands can use them (install
, mknod
...).
Some text variants can come in handy: if you simply want to ensure a file can be executed by anyone, chmod a+x
will do that, regardless of what the other permissions are. Likewise, +X
adds the execute permission only if one of the execute permissions is already set or the file is a directory; this can be handy for restoring permissions globally without having to special-case files v. directories. Thus, chmod -R ug=rX,u+w,o=
is equivalent to applying chmod -R 750
to all directories and executable files and chmod -R 640
to all other files.
Best Answer
setuid
sets the effective uid euid.setgid
set the effective gid egid.In both cases the callers uids and gids will stay in place. So roughly you can say that you will get that uid/gid in addition to the callers uid and (active) gid.
Some programs can differentiate that very well.
If you log into a system, then
su
to root and then issue awho am i
you will see your "old" account.su
is one of these suid-binaries, that will change the euid.