SELinux 유효성 검사

Android는 OEM에서 자체 구현한 SELinux를 철저히 테스트할 것을 적극적으로 권장합니다. 제조업체가 SELinux를 구현하려면 먼저 테스트 기기 풀에 새 정책을 적용해야 합니다.

새 정책을 적용한 후에는 getenforce 명령어를 실행하여 SELinux가 기기에서 제대로 된 모드로 동작하는지 확인합니다.

그러면 전역 SELinux 모드가 시행 또는 허용으로 출력됩니다. 각 도메인의 SELinux 모드를 확인하려면 관련 파일을 검사하거나 /platform/system/sepolicy/tools/에 위치한 최신 버전의 sepolicy-analyze에 적절한 플래그(-p)를 추가하여 실행합니다.

거부 로그 판독

오류를 확인하세요. 이 오류는 이벤트 로그로 dmesglogcat에 라우팅되고 기기에서 로컬로 조회할 수 있습니다. 제조업체는 공개 출시 전 허용 모드를 통해 기기에서 SELinux가 dmesg에 출력한 내용을 검사하고 설정을 조정한 후, 시행 모드로 최종 전환합니다. SELinux 로그 메시지는 avc:를 포함하므로 grep을 이용해 쉽게 찾을 수 있습니다. cat /proc/kmsg를 실행하여 진행 중인 거부 로그를 캡처하거나 cat /sys/fs/pstore/console-ramoops를 실행하여 이전 부팅에서 남아 있는 거부 로그를 캡처하는 것이 가능합니다.

SELinux 오류 메시지에는 부팅이 완료된 후 로그를 지나치게 채우지 않도록 속도 제한이 적용됩니다. 관련 메시지를 모두 보려면 adb shell auditctl -r 0를 실행하여 이 제한을 해제할 수 있습니다.

제조업체는 출력된 결과를 이용하여 시스템 사용자나 구성요소가 SELinux 정책을 위반했을 때 쉽게 식별할 수 있습니다. 그런 다음 소프트웨어와 SELinux 정책 중 하나 또는 둘 다를 변경하여 잘못된 동작을 바로잡을 수 있습니다.

특히 이러한 로그 메시지는 시행 모드에서 어떤 프로세스가 실패할지와 그 원인을 보여 줍니다. 예를 들면 다음과 같습니다.

avc: denied  { connectto } for  pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket

다음과 같이 출력 내용을 해석합니다.

  • { connectto }는 실행 중인 작업을 나타냅니다. 문장 마지막의 tclass(unix_stream_socket)와 함께 어떤 작업이 이루어졌는지 대략 알려 줍니다. 여기서는 무언가가 Unix 스트림 소켓에 연결하려고 했습니다.
  • scontext (u:r:shell:s0)는 어떤 컨텍스트가 작업을 시작했는지 알려 줍니다. 여기서는 셸로 실행된 무언가입니다.
  • tcontext (u:r:netd:s0)는 작업 대상의 컨텍스트를 알려 줍니다. 여기서는 netd가 소유한 unix_stream_socket입니다.
  • 윗부분의 comm="ping"에서는 거부 로그가 생성되었을 때 실행 중이었던 작업에 관해 추가적으로 알아볼 수 있습니다. 여기서 이 정보는 매우 유용합니다.

다른 예시를 확인해 보겠습니다.

adb shell su root dmesg | grep 'avc: '

출력:

<5> type=1400 audit: avc:  denied  { read write } for  pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file

이 거부 로그의 주요 요소는 다음과 같습니다.

  • 작업 - read write 또는 setenforce와 같이 시도한 작업이 대괄호로 강조표시되어 있습니다.
  • 행위자 - scontext(소스 컨텍스트) 항목은 행위자를 나타내며, 이 경우에는 rmt_storage 데몬입니다.
  • 객체 - tcontext(대상 컨텍스트) 항목은 실행 중인 객체, 즉 여기에서는 kmem을 나타냅니다.
  • 결과 - tclass(대상 클래스) 항목은 실행 중인 객체의 유형을 나타내고 여기에서는 chr_file(문자 기기)입니다.

사용자 및 커널 스택 덤프

경우에 따라 이벤트 로그에 포함된 정보만으로는 거부 출처를 파악하기에 충분하지 않습니다. 커널 및 사용자 공간을 비롯한 호출 체인을 수집하면 거부가 발생한 원인을 이해하는 데 유용한 경우가 많습니다.

최근 커널은 avc:selinux_audited라는 tracepoint를 정의합니다. Android simpleperf를 사용하여 이 tracepoint를 사용 설정하고 호출 체인을 캡처합니다.

지원되는 구성

  • Linux 커널 5.10 이상, 특히 Android 일반 커널 분기 mainlineandroid12-5.10이 지원됩니다. android12-5.4 브랜치도 지원됩니다. 다음과 같이 simpleperf를 사용하여 기기에 tracepoint가 정의되어 있는지 확인할 수 있습니다. adb root && adb shell simpleperf list | grep avc:selinux_audited 다른 커널 버전의 경우 커밋 dd8166230969bc를 선택할 수도 있습니다.
  • 디버깅하는 이벤트를 재현할 수 있어야 합니다. 부팅 시간 이벤트는 simpleperf를 사용해 지원되지 않습니다. 하지만 서비스를 다시 시작하여 이벤트를 트리거할 수는 있습니다.

호출 체인 캡처

첫 번째 단계는 다음과 같이 simpleperf record를 사용하여 이벤트를 기록하는 것입니다.

adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"

그러면 거부가 발생한 이벤트가 트리거됩니다. 그 후 기록을 중지해야 합니다. 이 예에서는 Ctrl-c를 사용하여 샘플을 캡처했습니다.

^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.

마지막으로 simpleperf report를 사용하여 캡처된 스택 트레이스를 검사할 수 있습니다. 예:

adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph"
[...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       -- __libc_init
          |
           -- main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback

위의 호출 체인은 통합 커널 및 사용자 공간 호출 체인입니다. 이를 통해 사용자 공간에서 트레이스가 시작되어 거부가 발생하는 커널에 이르기까지 트레이스를 시작하여 코드 흐름을 더 잘 파악할 수 있습니다. simpleperf에 관한 자세한 내용은 Simpleperf 실행 명령어 참조를 확인하세요.

허용 모드로 전환

SELinux 시행은 userdebug 또는 eng 빌드에서 ADB를 통해 사용 중지할 수 있습니다. 이렇게 하려면 먼저 adb root를 실행하여 ADB를 루트로 전환합니다. 그런 다음 SELinux 시행을 사용 중지하려면 다음을 실행합니다.

adb shell setenforce 0

또는 초기에 기기를 불러오는 동안 커널 명령줄에서 다음을 실행합니다.

androidboot.selinux=permissive
androidboot.selinux=enforcing

또는 Android 12의 bootconfig를 통해 다음을 실행합니다.

androidboot.selinux=permissive
androidboot.selinux=enforcing

audit2allow 사용

audit2allow 도구는 dmesg 거부 로그를 이에 대응하는 SELinux 정책 구문으로 변환합니다. 따라서 SELinux 개발 속도를 크게 높일 수 있습니다.

사용하려면 다음을 실행하세요.

adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy

그래도 각각의 잠재적인 추가사항이 권한을 넘어설 수 있는지 조사해야 합니다. 예를 들어 앞에서 보았던 rmt_storage 거부 로그를 audit2allow에 입력하면 다음과 같이 제안된 SELinux 정책 구문이 나옵니다.

#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };

이 정책은 rmt가 커널 메모리에 쓸 수 있도록 허용합니다. 이는 명백한 보안 취약점입니다. audit2allow 구문은 시작점일 뿐일 때가 많습니다 이 구문을 사용한 후에 좋은 정책을 완성하려면 제대로 된 매크로를 통합하고 소스 도메인과 대상의 라벨을 변경해야 할 수도 있습니다. 때로는 거부 로그를 검토한 결과 정책이 아닌 문제의 애플리케이션을 수정해야 할 때도 있습니다.