原文地址:http://blog.csdn.net/xuyunzhang/article/details/7814340
此文参考连接 http://forum.ubuntu.org.cn/weblog_entry.php?e=6900 且加上本人实际验证例子改编而成。
由于用户在UNIX下经常会遇到SUID、SGID的概念,而且SUID和SGID涉及到系统安全,所以用户也比较关心这个问题。
一、UNIX下关于文件权限的表示方法和解析
SUID 是 Set User ID, SGID 是 Set Group ID的意思。
UNIX下可以用ls -l命令来看到文件的权限。用ls命令所得到的表示法的格式是类似这样的:-rwxr-xr-x。
下面解析一下格式所表示的意思。
这种表示方法一共有十位: 9 8 7 6 5 4 3 2 1 0 - r w x r - x r - x
第9位表示文件类型,可以为p、d、l、s、c、b和-:
p表示命名管道文件;
d表示目录文件;
l表示符号连接文件;
-表示普通文件;
s表示socket文件;
c表示字符设备文件;
b表示块设备文件;
第8-6位、5-3位、2-0位分别表示文件所有者的权限,同组用户的权限,其他用户或组的权限,其形式为rwx:
r表示可读,可以读出文件的内容;
w表示可写,可以修改文件的内容;
x表示可执行,可运行这个程序;
没有权限的位置用-表示。
例子:
- # ls -l /dev
- crw-rw-r-- 1 admin123 root 45, 255 Jul 29 01:42 isdninfo
- crw-rw-r-- 1 admin123 root 1, 2 Jul 29 01:42 kmem
- lrwxrwxrwx 1 admin123 root 12 Jul 29 01:42 log -> /var/log/log
- crw-rw-r-- 1 admin123 root 1, 1 Jul 29 01:42 mem
- brw-rw-r-- 1 admin123 root 31, 0 Jul 29 01:42 mtdblock0
- brw-rw-r-- 1 admin123 root 31, 1 Jul 29 01:42 mtdblock1
- #
- # ls -l /var
- srwxr-xr-x 1 admin123 root 0 Jul 29 22:30 rtpproxy.sock
- drwxr-xr-x 2 admin123 root 0 Jul 29 22:30 run
- -rw-r--r-- 1 admin123 root 2 Jul 29 22:30 runhttpd
- drwxr-xr-x 4 admin123 root 0 Jan 1 2011 samba
- -rw------- 1 admin123 root 8192 Jan 1 2011 secrets.tdb
- drwxr-x--- 3 admin123 root 0 Jul 30 02:13 ser
- prw-rw---- 1 admin123 root 0 Jul 30 03:19 ser_fifo
- [quagga@localhost socket]$ ls -l openfile.c
- -rw-rw---- 1 zhang zhang 1770 2012-07-29 21:15 openfile.c
- [zhang@localhost ctest]$ ls -l | grep socket
- drwxr-xrwx 3 zhang zhang 4096 2012-07-31 10:13 socket
表示文件socket是目录文件,文件的所有者是zhang用户,而zhang用户属于zhang组,
文件只有1个硬连接,长度是4096个字节,最后修改时间2012-07-315。所有者zhang对文件有读写执行权限,
zhang组的成员对文件有读和执行权限, 其他的用户对这个文件读写执行权限。
如果一个文件被设置了SUID或SGID位,会分别表现在所有者或同组用户的权限的可执行位上。
例如:
1、-rwsr-xr-x 表示SUID和所有者权限中可执行位被设置
2、-rwSr--r-- 表示SUID被设置,但所有者权限中可执行位没有被设置
3、-rwxr-sr-x 表示SGID和同组用户权限中可执行位被设置
4、-rw-r-Sr-- 表示SGID被设置,但同组用户权限中可执行位没有被社置。
其实在UNIX的实现中,文件权限用12个二进制位表示,如果该位置上的值是1
表示有相应的权限: 11 10 9 8 7 6 5 4 3 2 1 0 S G T r w x r w x r w x
第11位为SUID位,第10位为SGID位,第9位为sticky位,第8-0位对应于上面三组rwx位。
11 10 9 8 7 6 5 4 3 2 1 0 上面的
-rwsr-xr-x的值为: 1 0 0 1 1 1 1 0 1 1 0 1
-rw-r-Sr--的值为: 0 1 0 1 1 0 1 0 0 1 0 0
给文件加SUID和SGID的命令如下:
chmod u+s filename 设置SUID位;
chmod u-s filename 去掉SUID设置;
chmod g+s filename 设置SGID位;
chmod g-s filename 去掉SGID设置;
另外一种方法是chmod命令用八进制表示方法的设置。如果明白了前面的12位 权限表示法也很简单。
二、SUID和SGID的详细解析
由于SUID和SGID是在执行程序(程序的可执行位被设置)时起作用,而可执行位只对普通文件和目录文件有意义,
所以设置其他种类文件的SUID和SGID位是没有多大意义的。首先讲普通文件的SUID和SGID的作用。
例子:
- [quagga@localhost socket]$ ls -l mycat
- -rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
- [quagga@localhost socket]$
- [quagga@localhost socket]$ ls -l openfile.c
- -rw-rw---- 1 zhang zhang 1770 2012-07-29 21:15 openfile.c
显然普通文件mycat和openfile.c是属于用户zhang的,任何用户都可以执行这个mycat程序,
quagga没有读openfile.c的权限(只有用户zhang以及zhang的同组成员有读openfile.c的权限)。
UNIX的内核是根据什么来确定一个进程对资源的访问权限的呢?
是这个进程的运行用户的(有效)ID,包括user id和group id。
用户可以用id命令来查到自己的或其他用户的user id和group id。
- [quagga@localhost socket]$ id root
- uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
- [quagga@localhost socket]$
- [quagga@localhost socket]$ id zhang
- uid=500(zhang) gid=500(zhang) groups=500(zhang)
- [quagga@localhost socket]$
- [quagga@localhost socket]$ id quagga
- uid=501(quagga) gid=501(quagga) groups=501(quagga)
- [quagga@localhost socket]$
除了一般的user id 和group id外,还有两个称之为effective的id,就是有效id,
上面的四个id表示为:uid,gid,euid,egid。
内核主要是根据euid和egid来确定进程对资源的访问权限。
一个进程如果没有SUID或SGID位,则euid=uid egid=gid,分别是运行这个程序的用户的uid和gid。
- [quagga@localhost socket]$ ls -l openfile.c
- -rw-rw---- 1 zhang zhang 1770 2012-07-29 21:15 openfile.c
- [quagga@localhost socket]$
- [quagga@localhost socket]$ ls -l mycat
- -rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
- [quagga@localhost socket]$
- [quagga@localhost socket]$ which cat
- /bin/cat
- [quagga@localhost socket]$ ls -l /bin/cat
- -rwxr-xr-x 1 root root 55184 2009-03-02 21:57 cat
- [quagga@localhost socket]$
- [quagga@localhost socket]$ cat openfile.c
- cat: openfile.c: Permission denied
- [quagga@localhost socket]$
- [quagga@localhost socket]$ ./mycat openfile.c
- cannot open openfile.c[quagga@localhost socket]$
用户quagga的uid和gid分别为501和501,用户zhang的uid和gid为500和500,
由于mycat/cat程序没有设置SUID或SGID位,quagga运行mycat程序形成的进程的euid=uid=501,egid=gid=501,
内核根据这些值来判断进程对资源访问的限制,其实就是用户quagga对资源访问的权限,和zhang没关系。
(quagga对openfile.c没有读权限, 所以 mycat/cat程序去读openfile.c时会cannot open openfile.c/openfile.c: Permission denied,
尽管用户zhang以及zhang的同组成员有这个权限)
如果一个程序设置了SUID("chmod u+s mycat"),则euid和egid变成被运行的程序的所有者(zhang)的uid和gid,
例如quagga用户运行mycat,euid=500,egid=500,uid=501,gid=501,
- [zhang@localhost socket]$ ls -l openfile.c
- -rw-rw---- 1 zhang zhang 1770 2012-07-29 21:15 openfile.c
- [zhang@localhost socket]$
- [zhang@localhost socket]$ ls -l mycat
- -rwxrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
- [zhang@localhost socket]$
- [zhang@localhost socket]$ chmod u+s mycat
- [zhang@localhost socket]$
- [zhang@localhost socket]$ ls -l mycat
- -rwsrwxr-x 1 zhang zhang 7421 2012-07-29 21:16 mycat
- [zhang@localhost socket]$
则用户quagga运行这个进程,quagga具就具有它的属主zhang(uid=500)的资源访问权限(读openfile.c的权限)。
[quagga@localhost socket]$ ./mycat openfile.c
- [openfile.c-43]: sockfd[1]=4, fd=3
- [my_open-78]: sockfd[0]=3, sockfd[1]=4, fd=4, c=x
-
-
-
-
-
-
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
-
- ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd);
-
- int main(int argc, char **argv)
- {
- int fd = -1, n = 0;
- char buf[10240] = {0};
-
- if (argc != 4)
- {
- fprintf(stderr, "openfile <sockfd#> <filename> <mode>\n");
- exit(255);
- }
-
- if ( (fd = open(argv[2], atoi(argv[3]))) < 0 )
- {
- exit( (errno > 0) ? errno : 255 );
- }
-
-
-
-
-
-
- if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
-
- {
- exit( (errno > 0) ? errno : 255 );
- }
-
- fprintf(stderr, "[%s-%d]: sockfd[1]=%d, fd=%d\n", __FILE__, __LINE__, atoi(argv[1]), fd );
-
- exit(0);
- }
-
- ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
- {
- struct msghdr msg;
- struct iovec iov[1];
-
- #if 1
- union
- {
- struct cmsghdr cm;
- char control[CMSG_SPACE(sizeof(int))];
- } control_un;
-
- struct cmsghdr *cmptr;
-
- msg.msg_control = control_un.control;
- msg.msg_controllen = sizeof(control_un.control);
-
- cmptr = CMSG_FIRSTHDR(&msg);
- cmptr->cmsg_len = CMSG_LEN(sizeof(int));
- cmptr->cmsg_level = SOL_SOCKET;
- cmptr->cmsg_type = SCM_RIGHTS;
- *((int *) CMSG_DATA(cmptr)) = sendfd;
- #else
- msg.msg_accrights = (caddr_t) &sendfd;
- msg.msg_accrightslen = sizeof(int);
- #endif
-
- msg.msg_name = NULL;
- msg.msg_namelen = 0;
-
- iov[0].iov_base = ptr;
- iov[0].iov_len = nbytes;
- msg.msg_iov = iov;
- msg.msg_iovlen = 1;
-
- return(sendmsg(fd, &msg, 0));
- }
- [quagga@localhost socket]$
SUID的作用就是这样:让本来没有相应权限的用户运行这个程序时,可以访问他没有权限访问的资源。
passwd就是一个很鲜明的例子。
SUID的优先级比SGID高,当一个可执行程序设置了SUID,则SGID会自动变成相应的egid。
下面讨论一个例子:
UNIX系统有一个/dev/kmem的设备文件,是一个字符设备文件,里面存储了核心程序要访问的数据,包括用户的口令。
所以这个文件不能给一般的用户读写,权限设为:
# ls -l /dev/kmem
- cr--r----- 1 root system 2, 1 May 25 1998 kmem
但ps等程序要读这个文件,而ps的权限设置如下:
# ls -l /bin/ps
- -r-xr-sr-x 1 bin system 59346 Apr 05 1998 ps
这是一个设置了SGID的程序,而ps的用户是bin,不是root,所以不能设置SUID来访问kmem,但大家注意了,
bin和root都属于system组,而且ps设置了SGID,一般用户执行ps,就会获得system组用户的权限,而文件kmem的
同组用户的权限是可读,所以一般用户执行ps就没问题了。但有些人说,为什么不把ps程序设置为root用户的程序,
然后设置SUID位,不也行吗?这的确可以解决问题,但实际中为什么不这样做呢?
因为SGID的风险比SUID小得多,所以出于系统安全的考虑,如果可能的话,应该尽量用SGID代替SUID的程序。
下面来说明一下SGID对目录的影响。
如果一个目录设置了SGID位,那么某用户(此用户对这个目录有写权限的话)在这个目录所建立的文件的组
都会自动转为这个目录的属主所在的组,而文件所有者不变,还是属于建立这个文件的用户。
- [zhang@localhost ctest]$ whoami
- zhang
- [zhang@localhost ctest]$ chmod o+w socket
- [zhang@localhost ctest]$ ls -l | grep socket
- drwxr-xrwx 3 zhang zhang 4096 2012-07-31 10:12 socket
- [zhang@localhost ctest]$ chmod g+s socket
- [zhang@localhost ctest]$
- [zhang@localhost ctest]$ ls -l | grep socket
- drwxr-srwx 3 zhang zhang 4096 2012-07-31 10:13 socket
-
- [quagga@localhost socket]$ whoami
- quagga
- [quagga@localhost socket]$ touch quagga
- [quagga@localhost socket]$ ls -l quagga
- -rw-rw-r-- 1 quagga zhang 0 2012-07-31 11:56 quagga
- [quagga@localhost socket]$
如果一个目录没有设置SGID位,那么某用户(此用户对这个目录有写权限的话)在这个目录下所建立文件的组
还是建立这个文件的用户所在的组,文件所有者还是属于建立这个文件的用户。
- [zhang@localhost ctest]$ whoami
- zhang
- [zhang@localhost ctest]$ chmod g-s socket
- [zhang@localhost ctest]$ ls -l | grep socket
- drwxr-xrwx 3 zhang zhang 4096 2012-07-31 11:56 socket
- [zhang@localhost ctest]$
-
- [quagga@localhost socket]$ whoami
- quagga
- [quagga@localhost socket]$ touch quagga1
- [quagga@localhost socket]$ ls -l quagga*
- -rw-rw-r-- 1 quagga zhang 0 2012-07-31 11:56 quagga
- -rw-rw-r-- 1 quagga quagga 0 2012-07-31 11:57 quagga1
- [quagga@localhost socket]$
- [quagga@localhost socket]$
附录:
mycat.c 源码:
-
-
-
-
-
-
- #include <stdio.h>
- #include <stdlib.h> // for exit
- #include <unistd.h> // for write
- #include <sys/wait.h> // for WEXITSTATUS
- #include <sys/un.h> // for socketpair
- #include <sys/socket.h> // for socketpair
- #include <sys/types.h>
- #include <sys/stat.h> //for open
- #include <fcntl.h>
- #include <errno.h>
-
- int my_open(const char *pathname, int mode);
- ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd);
-
- int main(int argc, char **argv)
- {
- int fd, n;
- char buf[10240];
-
- if (argc != 2)
- {
- fprintf(stderr, "usage: mycat <pathname>.\n");
- return 0;
- }
-
- if ( (fd = my_open(argv[1], O_RDONLY)) < 0)
- {
- fprintf(stderr, "cannot open %s", argv[1]);
- return 0;
- }
-
-
- if( (n = read(fd, buf, sizeof(buf))) > 0)
- {
- write(STDOUT_FILENO, buf, n);
- }
- else
- {
- perror("read\n");
- }
-
- exit(0);
- }
-
- int my_open(const char *pathname, int mode)
- {
- int fd, sockfd[2], status;
- pid_t childpid;
- char c, argsockfd[10], argmode[10];
-
- socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
-
- if ( (childpid = fork()) == 0)
- {
- close(sockfd[0]);
- snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
- snprintf(argmode, sizeof(argmode), "%d", mode);
- execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
- fprintf(stderr, "execl error.\n");
- }
-
-
- close(sockfd[1]);
-
-
- waitpid(childpid, &status, 0);
-
- if (WIFEXITED(status) == 0)
- {
- fprintf(stderr, "child did not terminate.\n");
- return 0;
- }
-
- if ( (status = WEXITSTATUS(status)) == 0)
- {
- read_fd(sockfd[0], &c, 1, &fd);
-
- fprintf(stderr, "[%s-%d]: sockfd[0]=%d, sockfd[1]=%d, fd=%d, c=%c\n", __FUNCTION__, __LINE__, sockfd[0], sockfd[1], fd, c );
-
- }
- else
- {
- errno = status;
- fd = -1;
- }
-
- close(sockfd[0]);
- return(fd);
- }
-
- ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
- {
- struct msghdr msg;
- struct iovec iov[1];
- ssize_t n;
-
- #if 1
- union
- {
- struct cmsghdr cm;
- char control[CMSG_SPACE(sizeof(int))];
- } control_un;
-
- struct cmsghdr *cmptr;
-
- msg.msg_control = control_un.control;
- msg.msg_controllen = sizeof(control_un.control);
- #else
- int newfd;
-
- msg.msg_accrights = (caddr_t) &newfd;
- msg.msg_accrightslen = sizeof(int);
- #endif
-
- msg.msg_name = NULL;
- msg.msg_namelen = 0;
-
- iov[0].iov_base = ptr;
- iov[0].iov_len = nbytes;
- msg.msg_iov = iov;
- msg.msg_iovlen = 1;
-
- if ( (n = recvmsg(fd, &msg, 0)) <= 0)
- return(n);
-
- #if 1
- if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int)))
- {
- if (cmptr->cmsg_level != SOL_SOCKET)
- {
- fprintf(stderr, "control level != SOL_SOCKET\n");
- }
-
- if (cmptr->cmsg_type != SCM_RIGHTS)
- {
- fprintf(stderr, "control type != SCM_RIGHTS\n");
- }
- *recvfd = *((int *) CMSG_DATA(cmptr));
- }
- else
- {
- *recvfd = -1;
- }
- #else
-
- if (msg.msg_accrightslen == sizeof(int))
- *recvfd = newfd;
- else
- *recvfd = -1;
-
- #endif
-
- return(n);
- }
openfile.c源码:
-
-
-
-
-
-
-
-
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
-
- ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd);
-
- int main(int argc, char **argv)
- {
- int fd = -1, n = 0;
- char buf[10240] = {0};
-
- if (argc != 4)
- {
- fprintf(stderr, "openfile <sockfd#> <filename> <mode>\n");
- exit(255);
- }
-
- if ( (fd = open(argv[2], atoi(argv[3]))) < 0 )
- {
- exit( (errno > 0) ? errno : 255 );
- }
-
-
-
-
-
-
- if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
-
- {
- exit( (errno > 0) ? errno : 255 );
- }
-
- fprintf(stderr, "[%s-%d]: sockfd[1]=%d, fd=%d\n", __FILE__, __LINE__, atoi(argv[1]), fd );
-
- exit(0);
- }
-
- ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
- {
- struct msghdr msg;
- struct iovec iov[1];
-
- #if 1
- union
- {
- struct cmsghdr cm;
- char control[CMSG_SPACE(sizeof(int))];
- } control_un;
-
- struct cmsghdr *cmptr;
-
- msg.msg_control = control_un.control;
- msg.msg_controllen = sizeof(control_un.control);
-
- cmptr = CMSG_FIRSTHDR(&msg);
- cmptr->cmsg_len = CMSG_LEN(sizeof(int));
- cmptr->cmsg_level = SOL_SOCKET;
- cmptr->cmsg_type = SCM_RIGHTS;
- *((int *) CMSG_DATA(cmptr)) = sendfd;
- #else
- msg.msg_accrights = (caddr_t) &sendfd;
- msg.msg_accrightslen = sizeof(int);
- #endif
-
- msg.msg_name = NULL;
- msg.msg_namelen = 0;
-
- iov[0].iov_base = ptr;
- iov[0].iov_len = nbytes;
- msg.msg_iov = iov;
- msg.msg_iovlen = 1;
-
- return(sendmsg(fd, &msg, 0));
- }
//todo