此文参考连接 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
/*
* this program is excuted by program of "mycat",
* execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
*
*/
#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 );
}
/*
n = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
*/
if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
// if ( write(atoi(argv[1]), &fd, sizeof(int)) < 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 源码:
/*
* file name: mycat.c
* this program is from <unix network program(volume 1 third edition)> 15.7 "unix domain socket"
*/
#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;
}
// while ( (n = read(fd, buf, sizeof(buf))) > 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) /* child process */
{
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");
}
/* parent process */
close(sockfd[1]);
/* wait for the child to terminate */
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);
//read(sockfd[0], &fd, sizeof(int));
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; /* set errno value from child's 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; /* descriptor was not passed */
}
#else
/* *INDENT-OFF* */
if (msg.msg_accrightslen == sizeof(int))
*recvfd = newfd;
else
*recvfd = -1; /* descriptor was not passed */
/* *INDENT-ON* */
#endif
return(n);
}
openfile.c源码:
/*
* file name: openfile.c
* this program is excuted by program of "mycat",
* execl("./openfile", "openfile", argsockfd, pathname, argmode, (char *) NULL);
*
*/
#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 );
}
/*
n = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
*/
if ( write_fd(atoi(argv[1]), "x", 1, fd) < 0 )
// if ( write(atoi(argv[1]), &fd, sizeof(int)) < 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