[리눅스] 코드로 이해하는 저장된 사용자 ID(Saved UID)가 있는 이유
저장된 사용자 ID - Saved UID
Saved UID를 이해하기 위해서는 실제 사용자 ID(Real UID)와 유효 사용자 ID(Effective UID)에 대한 이해가 깔려있어야합니다. 아직 개념이 안잡혀있다면 아래의 포스팅을 먼저 보고 오시면 좋겠습니다.
https://reakwon.tistory.com/228
Saved UID가 없다고 생각하고 Real UID와 Effective UID만 있다고 가정 해보세요. 그리고 setresuid 역시 suid를 설정하는 함수이니 없다고 가정해보세요. 권한이 확장된 프로그램에서의 euid를 권한이 확장된 euid라고 부르겠습니다. 다른 사용자의 권한을 얻은 상태에서 다시 권한을 축소 시켜야할때, 그러니까 실사용자 ruid로 돌려야할 상황이 생길 때 단순히 ruid로만 돌리면 나중에 다시 유효 사용자 권한이 필요할 때 돌아올 방법이 없습니다. 그러니까 euid, ruid를 왔다리 갔다리 유도리있게 스위칭할수가 없다는 뜻입니다.
- setuid를 통해서 권한이 확장된 euid로 다시 바꿀 수 있을까요? 현재 ruid=euid입니다. suid는 없다고 가정했으니, ruid로만 변경될 수 있는데, 이는 권한이 확장된 euid가 아니잖아요. setuid로는 바꿀 수 없습니다.
- seteuid 역시 마찬가지인데요. ruid 혹은 suid로 돌아갈 수 있는데 지금은 suid가 없다고 가정했으니 역시 setuid의 결과와 같습니다. 바꿀 수 없죠.
- setreuid도 마찬가지입니다. ruid가 현재 euid이기 때문이죠. setreuid는 현재의 ruid 혹은 euid로만 변경되는데, 지금 상황은 ruid가 euid와 같죠.
즉, 저장된 사용자 ID가 없이 ruid와 euid만 존재하면 현재 ruid = euid가 생길 때 다시 확장된 권한의 euid로 돌아갈 방법이 없다는 뜻입니다. 그래서 따로 그러한 확장된 권한의 euid를 저장해 놓아야하는데, 이를 위한 uid가 바로 saved-user id입니다.
그래서 이전 지난 포스팅에 suid가 초기의 euid와 같다는 점을 주시하라고 한겁니다. 저의 큰 그림 아시겠어요? 이제 saved라는 의미가 왜 붙었는지 아시겠죠?
예를 들어, root 권한의 프로그램에서 잠시 root 권한을 뺄 때를 생각해봅시다. 위와 같은 상황은 발생하겠죠. 아무리 root가 프로그램 사용권한을 자신의 것으로 쓰게 끔 허락했어도 특정 파일에 대한 접근 권한을 막을 상황이 생길겁니다. 이런 경우 seteuid를 사용자의 ruid로 돌려줍니다. 현재 ruid = 실 사용자 id, 현재 euid = 실 사용자 id 그러다가 그 후 다시 root 권한으로 작업해야할 때는 seteuid를 다시 root로 돌려야하겠죠. 그런데 현재 상황에서 ruid는 실 사용자의 id, 그리고 euid도 실 사용자 id인데, 어떻게 돌릴 수 있죠? suid는 아까 봤듯이 euid와 같았죠. 네, suid 덕분에 다시 유효사용자 id를 root로 돌릴 수 있게 됩니다.
아래의 코드는 root.txt라는 root만든 텍스트 파일을 euid를 변경해가며 읽는 소스 코드입니다. 실행하면서 어떤 현상이 발생하는지 관찰해보면 suid가 왜 쓰이는지 알 수 있을 겁니다.
//savedUID.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
void readfile(){
int n;
char buf[32]={0,};
int fd = open("root.txt", O_RDWR);
if(fd < 0){
printf("open error(%s)\n", strerror(errno));
return;
}
n = read(fd, buf, 32);
if(n < 0){
printf("read error(%s)\n", strerror(errno));
close(fd);
return;
}
printf("%s", buf);
close(fd);
}
void pr_resuid(){
int ruid, euid, suid;
if(getresuid(&ruid, &euid, &suid) < 0){
printf("setresuid error\n");
exit(0);
}
printf("ruid:%d, euid:%d, suid:%d\n",
ruid, euid, suid);
}
int main(){
printf("초기 uid\n");
pr_resuid();
printf("file 읽기 시도 > ");
readfile();
printf("\n");
printf("euid를 %d로 전환\n", getuid());
if(seteuid(getuid()) < 0){
printf("seteuid error\n");
return 1;
}
pr_resuid();
printf("file 읽기 시도 > ");
readfile();
printf("\n");
printf("euid를 root로 전환\n");
seteuid(0);
printf("file 읽기 시도 > ");
readfile();
pr_resuid();
}
우선 root의 유효사용자 id를 root로 돌려야하기 때문에 권한을 줘야겠군요. gcc 경고에 대한 문구는 가볍게 쌩까도록 합시다. 아참, 그리고 root권한의 파일을 하나 만들어야겠네요. root.txt를 읽어야하잖아요.
root# gcc savedUID.c
savedUID.c: In function ‘pr_resuid’:
savedUID.c:32:12: warning: implicit declaration of function ‘getresuid’; did you mean ‘setreuid’? [-Wimplicit-function-declaration]
32 | if(getresuid(&ruid, &euid, &suid) < 0){
| ^~~~~~~~~
| setreuid
root# chmod u+s a.out
root# echo "this is a file made by root" > root.txt
계정을 ubuntu로 전환하고 파일을 실행하면 어떤 결과가 나올까요?
root# su ubuntu
ubuntu$ ./a.out
초기 uid
ruid:1000, euid:0, suid:0
file 읽기 시도 > this is a file made by root
euid를 1000로 전환
ruid:1000, euid:1000, suid:0
file 읽기 시도 > open error(Permission denied)
euid를 root로 전환
file 읽기 시도 > this is a file made by root
ruid:1000, euid:0, suid:0
euid를 잠시 ubuntu로 전환하고 파일을 읽을 땐 파일을 읽을 수 없습니다. 여기서 알 수 있는 것은 permission denied로 권한이 축소되었음을 알 수 있습니다. 그 후에는 euid를 root로 전환하여 파일을 읽을 수 있게 되었습니다. euid를 0으로 되돌릴 수 있는 이유는 suid가 0이기 때문이고 만약 suid 마저도 다른 uid로 변경되었다면 seteuid는 에러를 발생시킵니다.