안드로이드는 리눅스의 UID(User Id)와 GID(Group Id)를 사용하여 앱마다 다르게 권한을 설정할 수 있습니다.
예를 들어, 앱에 할당되는 UID는 Root 또는 System이 아니기 때문에 /system
폴더에 접근할 수 없습니다.
반대로, 앱이 GID media_rw
를 갖고 있으면 sdcard에 접근할 수 있습니다.
이런 방식으로 시스템은 앱마다 다른 권한을 부여할 수 있고, 보안을 유지할 수 있습니다.
UID(User Id)
UID는 사용자의 ID입니다. 안드로이드는 앱마다 다른 UID를 할당합니다. 이렇게 하는 이유는 서로 다른 앱의 데이터에 접근하지 못하도록 만들기 위해서 입니다. 앱은 자신의 데이터 폴더만 접근가능하고, 시스템이나 다른 앱의 폴더에 접근할 수 없습니다.
예를들어, 루팅된 디바이스의 shell에서 아래 처럼 앱의 데이터 폴더의 권한 및 UID를 볼 수 있습니다.
generic_x86:/data/data # ls -aln
# Permission [UID] [GID] [File name]
drwx------ 10 10126 10126 4096 2019-08-08 20:17 com.google.android.apps.maps
drwx------ 8 10108 10108 4096 2019-08-08 20:17 com.google.android.apps.messaging
drwx------ 8 10129 10129 4096 2019-08-08 20:17 com.google.android.youtube
왼쪽에 있는 퍼미션을 보면 User만 rwx 권한이 있고 Group과 Others는 어떤 권한도 없습니다. 그리고 UID를 보면 앱마다 다른 UID가 설정되어 있습니다. 따라서, Root를 제외한 모든 앱은 서로의 파일에 접근할 수 없습니다.
앱 UID 확인
adb shell에서 ps -ef
명령어를 입력하면 프로세스의 UID를 확인할 수 있습니다.
127|generic_x86:/ $ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 09:02 ? 00:00:01 init second_stage
audioserver 1706 1 0 09:06 ? 00:00:00 android.hardware.audio@2.0-service
cameraserver 1708 1 0 09:06 ? 00:00:00 android.hardware.camera.provider@2.4-service
media 1709 1 0 09:06 ? 00:00:00 android.hardware.cas@1.1-service
system 1710 1 0 09:06 ? 00:00:00 android.hardware.configstore@1.1-service
media 1711 1 0 09:06 ? 00:00:00 android.hardware.drm@1.0-service
tombstoned 1774 1 0 09:07 ? 00:00:00 tombstoned
radio 2110 1701 0 09:12 ? 00:00:07 com.android.phone
u0_a114 2113 1701 0 09:12 ? 00:00:02 com.google.android.inputmethod.latin
u0_a93 2114 1701 0 09:12 ? 00:00:01 com.google.android.apps.nexuslauncher
위에 보이는 UID의 이름들은 각각 다른 숫자로 할당되어 있습니다. UID 이름이 아닌 숫자로 보고 싶으면 ps -efn
명령어를 사용하면 됩니다.
GID(Group Id)
GID는 Group Id입니다. User는 1개의 Primary group과 1개 이상의 Supplementary group을 갖을 수 있습니다. 결론적으로, User는 여러개의 GID를 갖을 수 있습니다.
앱이 특정 파일에 대한 접근 권한이 필요하다면 그 권한과 관련된 GID를 받아야 합니다.
예를 들어, 만약 앱이 android.permission.INTERNET
퍼미션을 얻는다면 시스템은 앱에 GID inet
을 추가해 줍니다.
앱은 inet
으로 접근 가능한 영역에 접근할 수 있게 됩니다.
GID도 UID처럼 ls -al
명령어로 파일들의 권한을 확인할 수 있습니다. 다음은 /sdcard
파일들의 GID 정보입니다.(GID를 숫자로 보려면 ls -aln
을 사용하세요)
generic_x86:/sdcard # ls -al
# Permission [UID] [GID] [File name]
drwxrwx--x 2 root sdcard_rw 4096 2019-07-19 22:04 Alarms
drwxrwx--x 3 root sdcard_rw 4096 2019-07-19 22:04 Android
drwxrwx--x 2 root sdcard_rw 4096 2019-07-19 22:04 DCIM
drwxrwx--x 2 root sdcard_rw 4096 2019-08-01 23:36 Download
drwxrwx--x 2 root sdcard_rw 4096 2019-07-19 22:04 Movies
drwxrwx--x 2 root sdcard_rw 4096 2019-07-19 22:04 Music
....
위의 결과에서 왼쪽 퍼미션을 보면 User와 Group은 rwx를 권한이 있습니다.
따라서 앱이 /sdcard
영역에 접근하려면 root가 되거나 sdcard_rw
Group에 속해야 합니다.
사실 앱이 WRITE_EXTERNAL_STORAGE 권한을 받으면 sdcard_rw GID을 받지 않습니다. 실제로는 앱의 mountinfo 정보가 변경되어 /sdcard에 접근할 수 있게 됩니다.
그리고 참고로, 안드로이드 Q에서는 Scoped Storage로 인해 외부 저장소에 접근하는 권한 및 방식들이 이전과 조금 달라졌습니다.
앱 GID 확인
앱의 GID를 확인하는 것은 UID보다 조금 더 번거롭습니다. 먼저 확인하려는 UID를 알아야 합니다.
예를 들어, Launcher 앱의 GID를 확인하려면 아래와 같은 명령어로 UID를 찾습니다.
generic_x86:/ $ ps -ef | grep launcher
u0_a93 2114 1701 0 22:09:12 ? 00:00:01 com.google.android.apps.nexuslauncher
그리고 /proc/[pid]/status
파일을 확인하면 GID를 알 수 있습니다.
저의 경우 PID는 2114이고 확인한 결과는 아래와 같습니다.
generic_x86:/ $ cat /proc/2114/status
Name: s.nexuslauncher
Umask: 0077
State: S (sleeping)
Tgid: 2114
Ngid: 0
Pid: 2114
PPid: 1701
TracerPid: 0
Uid: 10093 10093 10093 10093
Gid: 10093 10093 10093 10093
FDSize: 128
Groups: 9997 20093 50093
위의 결과에서 Gid는 Primary group으로 1개만 설정되어있고, Groups는 Supplementary group으로 여러개의 GID가 설정되어 있습니다.
앱의 UID는 누가 언제 할당하는가?
앱의 UID는 앱이 설치될 때 결정됩니다. PackageManager
는 시스템에 앱을 설치할 때 앱마다 다른 ID를 부여해 줍니다.
시스템 앱이 아닌, 일반적인 3rd party 앱은 10000~20000 사이의 숫자로 UID가 할당됩니다. 하지만 shell, root, installd 등은 고정된 시스템 UID이며, 이런 예약된 UID들은 10000 미만의 값으로 설정됩니다.
고정된(예약된) UID는 안드로이드 코드 android_filesystem_config.h
에 정의되어 있습니다.
#define AID_RADIO 1001 /* telephony subsystem, RIL */
#define AID_BLUETOOTH 1002 /* bluetooth subsystem */
#define AID_GRAPHICS 1003 /* graphics devices */
#define AID_INPUT 1004 /* input devices */
#define AID_AUDIO 1005 /* audio devices */
#define AID_CAMERA 1006 /* camera devices */
#define AID_LOG 1007 /* log devices */
#define AID_COMPASS 1008 /* compass device */
....
하지만 앱마다 UID가 같은 경우도 있습니다. 안드로이드에 SharedUserId라는 개념이 있으며 여러 앱이 동일한 UID를 사용할 수 있게 합니다.
아래와 같이 앱의 AndroidManifest.xml에 sharedUserId
속성으로 ID를 정할 수 있습니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codechacha.sample"
android:sharedUserId="android.uid.codechacha">
여러 앱을 위와 동일하게 sharedUserId를 설정하면, 앱이 단말에 설치될 때 동일한 UID를 할당 받습니다. 중요한 것은, SharedUserId를 설정할 때 앱들끼리 동일한 Signature로 서명이 되어야 합니다. 그렇지 않으면 앱이 설치되지 않습니다.
앱의 GID는 누가 언제 할당하는가?
고정적인 GID는 앱의 UID와 동일하게 설정되며, 앱마다 GID가 더 추가될 수 있습니다. 위에서 소개한 것처럼, 시스템은 앱이 어떤 퍼미션을 갖고 있을 때 어떤 GID를 할당할 지 참고하는 테이블이 있습니다.
안드로이드 코드 frameworks/base/data/etc/platform.xml
가 그 테이블 입니다.
아래와 같이 퍼미션 당 어떤 GID가 할당될 지 적혀있습니다.
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
</permission>
<permission name="android.permission.LOOP_RADIO" >
<group gid="loop_radio" />
</permission>
위의 퍼미션이 앱에 할당되면 dumpsys 로그에서 GID가 할당되었는지 확인할 수 있습니다.
adb shell dumpsys package [package name]
을 입력하면 아래와 같은 로그가 출력되고 여기에 gids=[3003]
처럼 할당된 GID를 보여줍니다.
GID 3003은 inet입니다.
Packages:
Package [com.codechacha.sample] (e5cd86f):
userId=10085
pkg=Package{9a1a3a5 com.codechacha.sample}
codePath=/data/app/com.codechacha.sample-UC4OqmKDH97R9vhuk7qtDw==
......
install permissions:
android.permission.INTERNET: granted=true
User 0: ceDataInode=-4294952062 installed=true hidden=false suspended=false stopped=false notLaunched=false enabled=0 instant=false virtual=false
gids=[3003]
보안 측면에서 UID와 GID
안드로이드에는 퍼미션 시스템이 있습니다. 앱은 AndroidManifest.xml에 퍼미션을 선언할 수 있고, 사용자가 허락하면 시스템 내에서 그 권한과 관련된 API를 사용할 수 있습니다.
퍼미션 시스템과 별개로, 안드로이드는 앱마다 다른 권한을 부여하는 UID와 GID를 사용합니다. 예를 들어, 어떤 시스템 API를 호출할 때, API는 함수를 호출하는 UID를 체크하여 몇몇 UID만 그 API를 사용할 수 있게 합니다. 그래서 어떤 API는 UID가 system인 프로세스만 사용할 수 있는 것들도 있습니다.
그 외에 다른 권한 및 보안(Security)
그 외에 안드로이드는 selinux를 사용합니다. 앱이 파일에 접근할 수 있는 UID, GID 권한을 모두 갖고 있어도 selinux 권한이 없어 접근이 안될 수도 있습니다. selinux는 파일마다 label을 붙이며, 앱의 프로세스가 해당 label의 접근 권한이 없다면 읽거나 쓸 수 없습니다.
정리
리눅스의 UID와 GID가 안드로이드에서 어떻게 사용되는지 알아보았습니다. 또한 앱이 어떻게 UID와 GID를 얻게 되는지, 어떤 방식으로 권한을 확인할 수 있는지 알아보았습니다. 그리고 보안 측면에서 어떻게 사용되는지 알아보았습니다.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력