아래 포스팅보다 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

사용자 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와 관련한 함수들을 보면서 어떤 특징을 갖는지 확인해보도록 합시다. 현재 접속한 사용자가 누군지 쉽게 알아보게 하기 위해서 프롬프트 앞에 사용자 계정명을 같이 표시하겠습니다.

1. 사용자 uid 읽기 - getuid, geteuid, getresuid

#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);

getuid는 사용자의 진짜 레알 아이디인 ruid를 가져옵니다.
geteuid는 프로그램의 실행 중 권한 euid를 가져옵니다.
getresuid는 프로그램의 ruid, euid뿐만 아니라 suid까지 가져옵니다. 실패시 -1을 가져옵니다.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>

void pr_resuid(){
        uid_t ruid, euid, suid;
       
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid :%d, suid :%d\n",
                        ruid, euid, suid);

}
int main(){
        pr_resuid();
}

보통의 상황이라면 ruid와 euid, suid는 같게 될 겁니다. 아래와 같이 u+s를 주어 setuid를 설정합니다. 그리고 실행하면 ruid와 euid는 0입니다. 여기까지는 다들 예상 하실 겁니다.

root# ls -l a.out 
-rwxr-xr-x 1 root root 8968 Jun 19 11:27 a.out
root# chmod u+s a.out
root# ./a.out 
ruid : 0, euid :0, suid :0

만약 다른 사용자로 로그인하게 되면 아래와 같이 실제 ruid는 바뀌지 않으나 euid는 바뀌죠. 여기까지 다 아는 내용입니다. 단순히 getresuid를 통해서 세가지 uid(ruid, euid, suid)를 가져오는 예제일 뿐입니다. 이 함수는 3개의 uid인 ruid, euid, suid를 모두 가져올 수 있기 때문에 지금부터 아래의 예제들은 이 함수로 uid들을 출력하도록 하겠습니다. 아, suid는 맨 처음 euid와 같은 점은 눈여겨 보시기 바랍니다.

root# su ubuntu
ubuntu$ ./a.out 
ruid : 1000, euid :0, suid :0

 

2. uid 설정 함수들

setuid, setgid

#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만 바꿀 수 있습니다.

다음은 setuid의 특징을 알아보는 코드입니다.

 

//setuid.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void pr_resuid(){
        uid_t ruid, euid, suid;
       
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid :%d, suid :%d\n",
                        ruid, euid, suid);

}

int main(int argc, char *argv[]){
        uid_t uid;
        if(argc < 2){
                printf("%s uid(>=0)\n", argv[0]);
                return 0;
        }

        pr_resuid();

        uid = atoi(argv[1]);
        printf("setuid(%d)\n", uid);
        if(setuid(uid) == -1){
                printf("setuid error : %s\n", strerror(errno));
                exit(0);
        }

        pr_resuid();
}

ubuntu의 계정으로 컴파일 합니다. 이때 ruid는 1000번입니다.

ubuntu$ gcc setuids.c 
ubuntu$ chmod u+s a.out
ubuntu$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)

이 프로그램을 다른 계정으로 실행해보면 어떻게 될까요? euid 1000인 프로그램에서 실제 ruid가 다른 1001로 바꾸려고 할 때는 아래와 같이 에러가 발생합니다. 당연하겠죠. euid도 누군가가 함부로 남의 euid를 쓸 수는 없는 겁니다.

ubuntu$ ./a.out 1000
ruid : 1000, euid :1000, suid :1000
setuid(1000)
ruid : 1000, euid :1000, suid :1000

ubuntu$ ./a.out 1001
ruid : 1000, euid :1000, suid :1000
setuid(1001)
setuid error : Operation not permitted

 

아까 조건에서도 봤듯이 ruid 혹은 suid가 변경하려는 uid와 같아야 euid만 변경이 됩니다. 여기 1001인 유저 hello로 로그인해서 확인해보겠습니다.

ubuntu$ su hello
Password: 
hello$ id
uid=1001(hello) gid=1001(hello) groups=1001(hello) 

hello$ ./a.out 1000    <- ruid:1001, suid: 1000이므로 euid를 1000으로 변경 가능
ruid : 1001, euid :1000, suid :1000
setuid(1000)
ruid : 1001, euid :1000, suid :1000

hello$ ./a.out 1001  <- ruid:1001, suid: 1001이므로 euid를 1001으로 변경 가능
ruid : 1001, euid :1000, suid :1000
setuid(1001)
ruid : 1001, euid :1001, suid :1000

hello$ ./a.out 1       <- ruid:1001, suid: 1000이므로 1로 변경 불가 
ruid : 1001, euid :1000, suid :1000
setuid(1)
setuid error : Operation not permitted

그렇다면 만약 프로그램의 주인이 루트라면 위 상황은 어떻게 될까요? 아래 명령을 통해서 owner를 바꾸고 setuid도 같이 바꿔줍니다.

root# chown root:root a.out     <- 절대무적 루트가 나타나 owner를 자신으로
root# chmod u+s a.out               <- 실행시 root의 권한을 갖는다.

그리고 계정을 root로 바꿔서 실행해보면 ruid, euid, suid를 전부 바꾼다는 것을 알 수 있습니다. 이는 프로그램이 실행될 때 root의 권한으로 실행(초기 euid가 root) 되어지기 때문에 이라는 것을 설명드렸어요. 루트는 절대무적이니까요.

ubuntu$ ./a.out 0
ruid : 1000, euid :0, suid :0.     <- 현재 유효사용자 id(euid)는 root
setuid(0)
ruid : 0, euid :0, suid :0   <- euid가 root이기 때문에 모든id를 바꿀 수 있다.

ubuntu$ ./a.out 1000
ruid : 1000, euid :0, suid :0
setuid(1000)
ruid : 1000, euid :1000, suid :1000

ubuntu$ ./a.out 1001
ruid : 1000, euid :0, suid :0
setuid(1001)
ruid : 1001, euid :1001, suid :1001

ubuntu$ ./a.out 1
ruid : 1000, euid :0, suid :0
setuid(1)
ruid : 1, euid :1, suid :1

 

seteuid, setegid

#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를 차례차례 알아보는 코드와 위의 표 내용이 맞는 지 확인하는 실행 결과를 보여줍니다.

//setuids_test.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void pr_resuid(){
        uid_t ruid, euid, suid;

        if(getresuid(&ruid,&euid,&suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid : %d, suid : %d\n", 
                        ruid, euid, suid);

}
void setuid_test(){
        uid_t uid;
        printf("uid :");
        scanf("%d", &uid);

        if(setuid(uid) < 0){
                printf("setuid %d로 변경 불가 \n", uid);
        }
        pr_resuid();
}
void seteuid_test(){
        uid_t euid;
        printf("euid :");
        scanf("%d", &euid);
        if(seteuid(euid) < 0){
                printf("seteuid %d로 변경 불가 \n", euid);
        }
        pr_resuid();
}
void setreuid_test(){
        uid_t ruid, euid;
        printf("ruid euid :");
        scanf("%d %d", &ruid, &euid);
        if(setreuid(ruid, euid) < 0){
                printf("setreuid %d %d로 변경 불가\n", ruid, euid);
        }
        pr_resuid();
}
void setresuid_test(){
        uid_t ruid, euid, suid;
        printf("ruid euid suid :");
        scanf("%d %d %d", &ruid, &euid, &suid);
        if(setresuid(ruid, euid, suid) < 0){
                printf("setresuid %d %d %d로 변경 불가\n", ruid, euid, suid);
        }
        pr_resuid();
}
int main(){
        printf("현재 uid\n");
        pr_resuid();

        while(1){
                char c;
                printf("(1)setuid-test\n");
                printf("(2)seteuid-test\n");
                printf("(3)setreuid-test\n");
                printf("(4)setresuid-test\n");
                printf("(other)exit\n");

                scanf(" %c", &c);
                switch(c){
                        case '1':
                                setuid_test();
                                break;
                        case '2':
                                seteuid_test();
                                break;
                        case '3':
                                setreuid_test();
                                break;
                        case '4':
                                setresuid_test();
                                break;
                        default:
                                return 0;
                }

        }

}

 

아래의 상황은 ubuntu(1000)의 유효사용자 id를 갖는 위 프로그램을 hello(1001)라는 유저가 실행했을 때의 상황입니다.

hello$ ls -l a.out
-rwsr-xr-x 1 ubuntu ubuntu 13616 Jun 23 08:43 a.out
hello$ ./a.out 
현재 uid
ruid : 1001, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
1
uid :1001
ruid : 1001, euid : 1001, suid : 1000   <- euid만 변경됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
2
euid :1000
ruid : 1001, euid : 1000, suid : 1000  <- 현재 ruid, suid로만 euid만 변경됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
3
ruid euid :1000 1001
ruid : 1000, euid : 1001, suid : 1001   <- euid와 suid는 같은 값이 됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
3
ruid euid :1000 1000       <- setresuid(1000, 1000, 1000)과 같다.
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
2
euid :1001   <- 3개의 uid가 전부 같은 1000이므로 euid를 바꿀 어떤 방법이 없다
seteuid 1001로 변경 불가 
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
4
ruid euid suid :1000 1000 1001  <- 위와 같은 이유로 setresuid를 통해서도 바꿀 수없음
setresuid 1000 1000 1001로 변경 불가
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
4
ruid euid suid :1000 1000 1000  <- 이게 무슨 의미가 있을까
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
q         <- 분노의 종료

 

실제 사용자 ID(Real UID), 그리고 유효 사용자 ID(Effective UID)는 대충 알겠는데, saved UID는 어떤 경우에 사용이 되는 걸까요? 포스팅이 너무 길어지니 아래의 포스팅에서 설명하도록 하겠습니다.

https://reakwon.tistory.com/234

 

리눅스 - 코드로 이해하는 저장된 사용자 ID(Saved UID)가 있는 이유

저장된 사용자 ID - Saved UID Saved UID를 이해하기 위해서는 실제 사용자 ID(Real UID)와 유효 사용자 ID(Effective UID)에 대한 이해가 깔려있어야합니다. 아직 개념이 안잡혀있다면 아래의 포스팅을 먼저

reakwon.tistory.com

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

DM-verity 

Data Mapper Verity의 약자로 쓰이는데, 루트 파일 시스템(Root FS)의 검증에 쓰인다. 과거에 안드로이드에 쓰였지만 현재는 점차 임베디드에 쓰이기 시작한다. 기본 원리는 아래의 해시 트리(Hash-Tree)를 만드는 것인데, Data Block마다 Hash를 생성해서 가장 아래 level의 hash 노드를 만들기 시작하고 그 위 level의 hash 노드를 그 아래 level에 hash node를 이용하여 만들어 점차 트리를 완성시켜가는데, 이때 가장 맨 위의 Hash 노드를 Root Hash라고 하며 안전하게 보관되어져야한다. 이 Root Hash값을 이용해서 dm-verity를 검증해야하기 때문.   

이러한 Hash tree를 Merkel Tree라고도 한다. 

https://www.timesys.com/security/dm-verity-without-an-initramfs/

 

1. 블록 장치 생성 + 파일 시스템 설정

# truncate -s 10G data_partition.ext4
# mkfs -t ext4 data_partition.ext4
# mkdir mnt 
# mount -o loop data_partition.ext4 mnt/ 
# echo "hello" > mnt/one.txt 
# echo "integrity" > mnt/two.txt 
# umount mnt/ 
# truncate -s 100M hash_partition.verity

10G의 크기를 갖는 블록을 하나 생성하여 ext4 파일 시스템을 적용한다. 그리고 그 안에 one.txt, two.txt를 만들고 난 후 unmount를 한다.

이후 100M 크기의 hash의 값을 갖는 영역을 생성해낸다. 이곳에 dm-verity의 merkle-tree가 저장된다.

 

2. dm-verity format

# veritysetup -v --debug format data_partition.ext4 hash_partition.verity > root_hash.txt

 

root_hash.txt의 내용

# cryptsetup 2.2.2 processing "veritysetup -v --debug format data_partition.img hash_partition.img" 
# Running command format. 
# Allocating context for crypt device hash_partition.img. 
# Trying to open and read device hash_partition.img with direct-io. 
# Initialising device-mapper backend library. 
# Formatting device hash_partition.img as type VERITY. 
# Crypto backend (OpenSSL 1.1.1f  31 Mar 2020) initialized in cryptsetup library version 2.2.2. 
# Detected kernel Linux 5.13.0-52-generic x86_64. 
# Setting ciphertext data device to data_partition.img. 
# Trying to open and read device data_partition.img with direct-io. 
# Hash creation sha256, data device data_partition.img, data blocks 2621440, hash_device hash_partition.img, offset 1. 
# Using 4 hash levels. 
# Data device size required: 10737418240 bytes. 
# Hash device size required: 84557824 bytes. 
# Updating VERITY header of size 512 on device hash_partition.img, offset 0. 
VERITY header information for hash_partition.img 
UUID:               4da1ecb5-5111-4922-8747-5e867036d9de 
Hash type:          1 
Data blocks:        2621440 
Data block size:    4096 
Hash block size:    4096 
Hash algorithm:     sha256 
Salt:       	     f2790cf141405152cf61b6eb176128ad0676b41524dd32ac39760d3be2d495cf 
Root hash:          a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71 
# Releasing crypt device hash_partition.img context. 
# Releasing device-mapper backend. 
# Closing read write fd for hash_partition.img. 
Command successful.

Root Hash(a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71)의 내용은 trusted된 내용이어야하고, 안전하게 보관되어야한다.

 

3. dm-verity open

# veritysetup open \ 
>         data_partition.ext4 \ 
>         verity-test \ 
>         hash_partition.verity \ 
>         a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71

data_partition.ext4의 mapper를 생성한다.

/dev/mapper/verity-test 가 생성된 것을 확인할 수 있다.

# mkdir mnt 
# mount /dev/mapper/verity-test mnt/ 
mount: /home/ubuntu/ext4/mnt: WARNING: source write-protected, mounted read-only.
# cat mnt/one.txt mnt/two.txt 
hello
integrity

read-only로 mount 되었다.

 

4. dm-verity verify

user-space에서 검증이 가능하다.

  • 검증 성공 시
# veritysetup verify \
> /dev/mapper/verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71
#
  • 검증 실패 시

  hash의 값을 변경(끝 네자리 0f71 0f73)

# veritysetup verify \
> /dev/mapper/verity-test \
> hash_partition.verity \
> a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f73
Verification of root hash failed.

 

5. Corruption 발생 처리

dm-verity는 디스크를 binary level 수준으로 보호하기 때문에 한 비트라도 어긋나게 되면 corruption 발생 처리하며 mount되지 않는다.

disk를 read-write로 mount하는 것 만으로도 meta-data 변형이 일어나기 때문에 binary가 변경된다. 따라서 corruption을 일으키기 충분하다.

# umount mnt/ 
# veritysetup close verity-test 
# mount -o loop data_partition.ext4 mnt/ 
# umount mnt/ 
# veritysetup open \ 
>         data_partition.ext4 \ 
>         verity-test \ 
>         hash_partition.verity \ 
>         a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71 
Verity device detected corruption after activation 
root# mount /dev/mapper/verity-test mnt/ 
mount: /path/to/mnt: can't read superblock on /dev/mapper/verity-testroot## mount /dev/mapper/verity

 

dmesg를 확인하면 kernel에서 dm-verity의 log를 확인 할 수 있다.

[412036.212897] device-mapper: verity: 7:16: data block 0 is corrupted 
[412036.212996] device-mapper: verity: 7:16: data block 0 is corrupted 
[412036.213009] buffer_io_error: 91 callbacks suppressed 
[412036.213011] Buffer I/O error on dev dm-0, logical block 0, async page read 
[412036.223697] device-mapper: verity: 7:16: data block 0 is corrupted

 

5. dm-verity status

현재 mapping 상태를 확인한다.

# veritysetup status verity-test
/dev/mapper/verity-test is active.
  type:        VERITY
  status:      corrupted
  hash type:   1
  data block:  4096
  hash block:  4096
  hash name:   sha256
  salt:        f2790cf141405152cf61b6eb176128ad0676b41524dd32ac39760d3be2d495cf
  data device: /dev/loop11
  data loop:   /home/ubuntu/ext4/data_partition.ext4
  size:        20971520 sectors
  mode:        readonly
  hash device: /dev/loop10
  hash loop:   /home/ubuntu/ext4/hash_partition.verity
  hash offset: 8 sectors
  root hash:   a2a8fd07889deb10b4cdf53c01637ed373212cd7d0877a8aa9ae6fd4240f0f71

 

6. dm-verity close

mapping을 제거한다.

# veritysetup close verity-test

/dev/mapper에서 사라진것을 확인할 수 있다.

 

Reference)

https://www.timesys.com/security/dm-verity-without-an-initramfs/

 

DM-Verity Without an Initramfs - Timesys

Learn how you can implement file system verification on your embedded system without the use of an initramfs. This can significantly save boot time and storage requirements in many situations.

www.timesys.com

https://www.starlab.io/blog/dm-verity-in-embedded-device-security

반응형
블로그 이미지

REAKWON

와나진짜

,

디바이스 드라이버에 Argument 전달

디바이스 드라이버가 등록될때나 실행되고 있을때 인자를 전달할 수 있을까요? 예를 들면 우리가 ls 명령어를 실행할때 ls -l /etc/와 같이 -l /etc/와 같은 인자들을 전달하는 것처럼 말이죠. 응용 프로그램에서는 간단합니다. 우리는 알고 있죠. C언어를 사용한다면 main에서 argc와 args를 사용해서 목적을 달성 할 수 있다는 것을 말이죠. 

int main(int argc, char* argv[])

 

리눅스의 디바이스 드라이버에서도 가능합니다. 바로 아래의 매크로들을 이용하면 됩니다. 포스팅 아래에서는 아래의 매크로를 통해서 예제 코드를 구현합니다.

  • module_param(name, type, perm) : 변수 name을 설정합니다. name의 자료형은 type이고, perm은 권한을 나타냅니다.
  • module_param_array(name, type, num, perm) : 배열 버전입니다. num이 자료형의 배열 크기를 나타냅니다.
  • module_param_cb(name, &ops, &name, perm) : 만약 paremeter의 값이 바뀐 경우 알아차려야한다면, 이 매크로를 사용하면 됩니다. cb는 callback의 약자입니다.

 

그리고 이러한 파라미터를 읽거나 수정할때 권한(permission)을 지정할 수 있습니다. S_I를 Prefix로 하고, R(Read), W(Write), X(eXecute)를 의미하며 USR은 user, GRP는 group 권한을 의미합니다. |(OR)을 통해서 여러 권한을 줄 수 있습니다.

  • S_IRUSR
  • S_IWUSR
  • S_IXUSR
  • S_IRGRP
  • S_IWGRP
  • S_IXGRP

 

아래의 코드를 보면서 어떻게 위의 매크로들이 사용되는지 알아보도록 하겠습니다. 


소스코드 

//passing_params.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>


int value, arr_value[4];
char *name;
int cb_value = 0;

module_param(value, int, S_IWUSR|S_IRUSR);
module_param(name, charp, S_IRUSR|S_IWUSR);
module_param_array(arr_value,int, NULL, S_IRUSR|S_IWUSR);

// module_param_cb를 위한 setter
int notify_param(const char *val, const struct kernel_param *kp){
        int res = param_set_int(val, kp);
        if(res == 0){
                printk(KERN_INFO "Call back function called...\n");
                printk(KERN_INFO "New value of cb_value = %d\n",cb_value);
                return 0;
        }
        return -1;
}

const struct kernel_param_ops my_param_ops = {
        .set = &notify_param, //위에 정의한 setter
        .get = &param_get_int, // standard getter
};

module_param_cb(cb_value, &my_param_ops, &cb_value, S_IRUGO|S_IWUSR);

static int __init my_module_init(void){
        int i;
        printk(KERN_INFO "===== Print Params =====\n");
        printk(KERN_INFO "value = %d \n", value);
        printk(KERN_INFO "cb_value = %d \n", cb_value);
        printk(KERN_INFO "name = %s\n", name);
        for(i = 0; i < sizeof(arr_value)/sizeof(int); i++){
                printk(KERN_INFO "arr_value[%d] = %d \n", i, arr_value[i]);
        }
        return 0;
}

static void __exit my_module_exit(void){
        printk(KERN_INFO "Kernel Module Removed ...\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Reakwon");
MODULE_DESCRIPTION("A simple parameter test module");
MODULE_VERSION("1:1.0");

 

Makefile

obj-m += passing_params.o
KDIR = /lib/modules/$(shell uname -r)/build

all :
        make -C $(KDIR) M=$(shell pwd) modules

clean :
        make -C $(KDIR) M=$(shell pwd) clean

insmod로 parameter 전달

$ sudo insmod passing_params.ko value=4 name="reakwon" arr_value=1,2,3,4

dmesg를 확인하면 잘 전달되고 읽히는 것을 확인할 수 있습니다.

insmod parameter 전달

 


파라미터 업데이트시 Callback

만약 parameter를 디바이스 드라이버가 실행 중일때 변경하고 이에 따른 동작을 수행하고자 한다면 어떻게 할까요? module_param_cb가 이런 이유때문에 존재합니다. 

모듈의 parameter는 /sys/module 아래의 자신의 모듈 이름의 디렉토리 하위 parameters 디렉토리에서 관리가 됩니다. 확인해볼까요? 

$ ls /sys/module/passing_params/parameters/
arr_value  cb_value  name  value

 

그래서 parameters의 하위의 변수들의 값을 바꿀수 있습니다. 이때 callback 함수를 등록하면 호출이 되게 됩니다.  위의 전체 코드 중 이와 관련한 코드가 여깄습니다.

// module_param_cb를 위한 setter
int notify_param(const char *val, const struct kernel_param *kp){
        //...//
}

const struct kernel_param_ops my_param_ops = {
        .set = &notify_param, //위에 정의한 setter
        .get = &param_get_int, // standard getter
};

module_param_cb(cb_value, &my_param_ops, &cb_value, S_IRUGO|S_IWUSR);

 

kernel_param_ops가 callback을 관리하는 구조체이고 여기의 멤버로 .set, .get, .free가 있습니다. 

struct kernel_param_ops 
{
 int (*set)(const char *val, const struct kernel_param *kp);
 int (*get)(char *buffer, const struct kernel_param *kp);
 void (*free)(void *arg);
};

 

아래와 같이 parameter의 값을 변경해봅시다. 

$ sudo sh -c "echo 1010 > /sys/module/passing_args/parameters/cb_value"

 

그리고 dmesg를 통해서 kernel 메시지를 확인하면 우리가 등록한 notify_param Callback함수가 호출됨을 알 수 있습니다. 

callback 호출

 

 

이 포스팅은 embetronicx의 contents를 참고하여 작성된 포스팅입니다. 여기에 다 담지못하는 부분은 아래의 페이지에서 참고하시기 바랍니다. 앞으로도 embetronicx의 tutorial을 기반으로 작성할 예정입니다.

https://embetronicx.com/tutorials/linux/device-drivers/linux-device-driver-tutorial-part-3-passing-arguments-to-device-driver/

 

반응형
블로그 이미지

REAKWON

와나진짜

,