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가 왜 쓰이는지 알 수 있을 겁니다.
우선 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는 에러를 발생시킵니다.
사용자ID(Real User ID, Effective User ID, Saved User ID)와그룹ID(Real Group ID, Effective Group ID, Saved Group ID)
리눅스에서 사용자는 각 User ID라는 값을 갖습니다. 그리고 그 사용자는 어느 그룹의 일원이 되죠. 그래서 Group ID도 같이 갖습니다. 이렇게 id를 부여하는 이유는 각각 사람들마다 다른 권한을 갖고 있기 때문입니다. 각 사용자들은 자신들의 파일을 만들고 그 파일에 대해 권한을 부여합니다. 즉, 이 파일을 어디까지 허용할지 말이죠. 파일을 누구에게 까지 읽고 쓰는 것을 허락하느냐, 실행파일이라면 실행을 누구에게 까지 허락하느냐 이러한 허용 말입니다.
그런데 실행 파일이라면 특수한 권한 하나가 더 생깁니다. 이 파일을 실행할 경우에 갖는 권한, 즉 실행 할 때만 유효한 권한이요. 그게 뭐냐면 실행할 때만 소유주의 권한, 혹은 소유주 그룹의 권한으로 실행되는 권한입니다. 예를 들어서 파일을 읽는 어떤 프로그램이 있다고 칩시다. 그리고 이 프로그램의 소유주는 “기쁨”이라고 할게요. 어떤 사용자 “똘똘이”가 프로그램을 통해서 파일을 읽을 땐 똘똘이의 파일밖에 못 읽습니다. 기쁨이의 파일을 함부로 볼 수가 없죠. 근데 착한 기쁨이는 나의 프로그램을 쓸 때 나의 파일은 읽을 수 있게 특별한 권한을 이 프로그램에 준다면 똘똘이는 기쁨이의 파일도 읽게 될 수 있습니다. 이와 같은 상황을 권한이 확장되었다라고 합니다.
전문용어로 정리하면 아래와 같습니다. 그리고 여기서는 uid에만 포커스를 맞춰서 보겠습니다. 왜냐면 gid도 역시 uid와 같은 방식으로 동작되지 때문이죠.
UID 종류
설명
Real UID(User ID) ruid
실제 사용자 ID를 의미합니다. 여러분들이 로그인할 때 접속하는 그 user의 ID입니다. 이 실제 id에 대한 정보는 /etc/passwd 파일에 기록이 되어있지요. 줄여서 ruid라고 하겠습니다.
Effective UID(User ID) euid
프로그램 실행 시 갖는 권한을 의미하며 실행 파일의 소유주 권한입니다. 보통은 사용자 ruid와 같습니다. 실행파일의 user 실행 권한에 setuid 권한이 있을 경우에 달라집니다. 줄여서 euid라고 합니다.
Saved UID(User ID) suid
저장된 사용자의 ID라고 합니다. 프로그램에 권한을 더 쎈 권한을 주어야할 때나 권한을 더 줄여야할 때에 유기적으로 쓰이게 됩니다. 줄여서 suid라고 합니다. suid의 쓰임새에는 조금 나중에(맨 아래에서) 코드로 설명을 드리겠습니다.
GID 종류
설명
Real GID(Group ID) rgid
실제 사용자 그룹의 ID입니다. Ruid와 마찬가지로 로그인할때 부여되는 gid를 의미합니다. 줄여서 rgid라고 합니다.
Effective GID(Group ID) egid
유효 사용자 그룹의 ID입니다. 역시 보통은 rgid와 같습니다. 실행파일에 setgid비트가 켜져있으면 rgid와 달라질 수 있습니다.
Saved GID(Group ID) sgid
저장된 그룹의 ID입니다. 줄여서 sgid라고 합니다.
이제 uid와 관련한 함수들을 보면서 어떤 특징을 갖는지 확인해보도록 합시다. 현재 접속한 사용자가 누군지 쉽게 알아보게 하기 위해서 프롬프트 앞에 사용자 계정명을 같이 표시하겠습니다.
만약 다른 사용자로 로그인하게 되면 아래와 같이 실제 ruid는 바뀌지 않으나 euid는 바뀌죠. 여기까지 다 아는 내용입니다. 단순히 getresuid를 통해서 세가지 uid(ruid, euid, suid)를 가져오는 예제일 뿐입니다. 이 함수는 3개의 uid인 ruid, euid, suid를 모두 가져올 수 있기 때문에 지금부터 아래의 예제들은 이 함수로 uid들을 출력하도록 하겠습니다. 아, suid는 맨 처음 euid와 같은 점은 눈여겨 보시기 바랍니다.
#include <unistd.h>
int setuid(uid_t uid);
int setgid(uid_t gid);
setuid : 사용자가 루트일 경우 ruid, euid, suid를 전달된 인자인 uid로 변경할 수 있는 함수입니다(ruid = euid = suid = uid). 다만, 일반 조건에 맞는 유저일 경우만 제한적으로 euid만 자신의 uid로 변경됩니다.
자, 여기서 잘 생각해보세요. 아무나 ruid, 혹은 euid 혹은 suid를 바꾸면 될까요? 안될까요? 그러니까 아무나 본인의 학번이나 사번을 바꾼다고 생각해보세요. 있을 수가 없는 일이죠. 결론부터 말씀드리자면 setuid는 프로그램이 루트 권한으로 실행되면 루트사용자일 경우에 ruid, euid, suid 모두 변경합니다. 루트는 킹 중의 킹, 전설의 레전드입니다. 루트가 바꾸면 토 달지말고 그냥 바꾸는 겁니다.
다시 말하면 프로그램의 euid가 루트일때, setuid는 ruid, euid, suid 모두 바꿔버립니다. 즉, “유효 사용자 id가 root이면(현재 프로그램의 euid가 root이면) 모든 id(ruid, euid, suid)를 setuid를 통해서 바꿀 수 있다.” 입니다. 그런데 루트 사용자의 권한이 아닌 프로그램이고 그 안에서 setuid를 통해서 인자인 uid를 바꾸려하면, 사용자의 ruid 혹은 suid가 setuid의 인자로 전달되는 uid와 같을 때 euid만 변경이 됩니다. 그러니까 루트 사용자가 아닌 경우에는 실제 uid, 즉 ruid는 죽었다 깨어나도 변경할 수 없다는 의미입니다. euid만 바꿀 수 있습니다. 자신의 ruid 혹은 suid로만 말이죠.
얘기가 길었죠. 간단히 정리하면, 현재 유효 사용자 ID가 루트인 경우 ruid, euid, suid 가 원하는 uid로 바꿀 수 있습니다. 현재 유효 사용자 ID가 일반 유저인 경우 현재의 실 사용자 id(ruid) 혹은 현재의 저장된 사용자 id(suid)로 euid만 바꿀 수 있습니다.
#include <unistd.h>
int seteuid(uid_t euid);
int setegid(gid_t egid);
현재의 euid를 바꿉니다. 유효사용자가 root라면 euid를 원하는 값으로 바꿀 수 있습니다. 그런데 일반 사용자라면 euid는 현재의 ruid 혹은 현재의 suid로만 바꿀 수 있습니다. 실패시 -1을 반환합니다.
setreuid, setregid
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
위 함수들은 ruid, euid를 바꾸는 것 처럼 보이죠? 맞습니다. 그런데 추가로 suid도 바꿉니다. suid는 euid와 같이 바꿉니다. 이전에 보았던 setuid는 일반 유저가 ruid를 바꾸는게 불가능했었습니다. 그런데 이 함수는 가능합니다. 일반유저인 경우 setreuid 함수는 현재 ruid, euid(현재 suid와는 상관없습니다.) 중에서만 변경할 수 있습니다. 변경을 원치 않는 uid인 경우 -1을 넣어주면 됩니다.
ex) setreuid(-1, 1000);
실패시 -1을 반환합니다.
setresuid, setresgid
#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
만약 ruid, euid, suid를 모두 개별적으로 바꾸고 싶다면 이 함수를 사용하면 됩니다. 단, 이 함수도 아무 id나 바꿀 수는 없겠죠? root를 제외한 나머지 유저들은 ruid, euid, suid 각각의 값을 현재 ruid, euid, suid 중의 값으로만 바꿀 수 있습니다. 실패 시 -1을 반환합니다. 만약 현 uid를 그대로 유지하고 싶다면 해당하는 인자에 -1을 넣어주세요.
그럼 여기까지 uid 설정 함수들을 정리합니다.
현재ruid, euid, suid = ruid`, euid`, suid`
Ø유효 사용자ID = Root
함수
ruid
euid
suid
setuid
ruid`, euid`, suid와 상관없이 원하는 uid로 변경
seteuid
변경 불가
원하는 값으로 설정 가능
변경 불가
setreuid
ruid`, euid`, suid와 상관없이 원하는uid로 변경
euid와 같음
setresuid
ruid`, euid`, suid와 상관없이 원하는uid로 변경
Ø유효 사용자ID = 일반 유저
함수
ruid
euid
suid
setuid
변경 불가
ruid` 혹은suid`로 변경 가능
변경 불가
seteuid
변경 불가
ruid` 혹은suid`로 변경 가능
변경 불가
setreuid
ruid`, euid` 중 하나로 변경 가능
ruid`, euid` 중 하나로 변경 가능
euid와 같음
setresuid
ruid`, euid`, suid` 중 하나로 변경 가능
ruid`, euid`, suid` 중 하나로 변경 가능
ruid`, euid`, suid` 중 하나로 변경 가능
아래 setuid, setreuid, setresuid를 차례차례 알아보는 코드와 위의 표 내용이 맞는 지 확인하는 실행 결과를 보여줍니다.