"Linux мониторингийн BPF" ном

"Linux мониторингийн BPF" номСайн байна уу, Khabro оршин суугчид! BPF виртуал машин нь Линуксийн цөмийн хамгийн чухал бүрэлдэхүүн хэсгүүдийн нэг юм. Үүнийг зөв ашиглах нь системийн инженерүүдэд алдаа дутагдлыг олж, хамгийн төвөгтэй асуудлыг ч шийдвэрлэх боломжийг олгоно. Та цөмийн үйл ажиллагааг хянах, өөрчлөх программ бичих, цөм дэх үйл явдлыг хянах кодыг хэрхэн аюулгүй хэрэгжүүлэх болон бусад олон зүйлийг сурах болно. Дэвид Калавера, Лорензо Фонтана нар танд BPF-ийн хүчийг нээхэд тусална. Гүйцэтгэлийн оновчлол, сүлжээ, аюулгүй байдлын талаарх мэдлэгээ өргөжүүл. - Linux цөмийн үйл ажиллагааг хянах, өөрчлөхийн тулд BPF ашиглана уу. - Цөмийг дахин эмхэтгэх, системийг дахин ачаалах шаардлагагүйгээр цөмийн үйл явдлыг найдвартай хянахын тулд код оруулна. — C, Go эсвэл Python дээр тохиромжтой кодын жишээг ашигла. - BPF хөтөлбөрийн амьдралын мөчлөгийг эзэмшиж хяналтаа аваарай.

Линуксийн цөмийн аюулгүй байдал, түүний онцлогууд ба Seccomp

BPF нь тогтвортой байдал, аюулгүй байдал, хурдыг алдагдуулахгүйгээр цөмийг өргөтгөх хүчирхэг аргыг өгдөг. Ийм учраас цөмийн хөгжүүлэгчид Seccomp программуудаар дэмжигдсэн Seccomp шүүлтүүрийг хэрэгжүүлэх замаар Seccomp дахь процессын тусгаарлалтыг сайжруулахын тулд түүний олон талт байдлыг ашиглах нь зүйтэй гэж үзсэн. Энэ бүлэгт бид Seccomp гэж юу болох, түүнийг хэрхэн ашиглах талаар тайлбарлах болно. Дараа нь та BPF програм ашиглан Seccomp шүүлтүүрийг хэрхэн бичих талаар сурах болно. Үүний дараа бид Линуксийн аюулгүй байдлын модулиудын цөмд багтсан BPF дэгээг авч үзэх болно.

Линуксийн аюулгүй байдлын модулиуд (LSM) нь аюулгүй байдлын янз бүрийн загваруудыг стандартчилагдсан байдлаар хэрэгжүүлэхэд ашиглаж болох багц функцээр хангадаг хүрээ юм. LSM-ийг Apparmor, SELinux, Tomoyo зэрэг цөмийн эх үүсвэрийн модонд шууд ашиглаж болно.

Линуксийн боломжуудын талаар ярилцаж эхэлцгээе.

Онцлог

Линуксийн чадавхийн мөн чанар нь тодорхой даалгаврыг гүйцэтгэхийн тулд ямар нэгэн давуу эрхгүй процесст зөвшөөрөл олгох хэрэгтэй, гэхдээ энэ зорилгоор suid ашиглахгүйгээр, эсвэл өөр аргаар процессыг давуу эрхтэй болгож, халдлага хийх боломжийг бууруулж, процесст тодорхой ажлуудыг гүйцэтгэх боломжийг олгодог. Жишээлбэл, хэрэв таны аппликейшн 80 гэх мэт эрх бүхий порт нээх шаардлагатай бол процессыг root болгон ажиллуулахын оронд та CAP_NET_BIND_SERVICE чадамжийг өгч болно.

main.go нэртэй Go програмыг авч үзье:

package main
import (
            "net/http"
            "log"
)
func main() {
     log.Fatalf("%v", http.ListenAndServe(":80", nil))
}

Энэ програм нь 80 порт дээр HTTP серверт үйлчилдэг (энэ нь давуу эрхтэй порт юм). Ихэвчлэн бид үүнийг эмхэтгэсний дараа шууд ажиллуулдаг:

$ go build -o capabilities main.go
$ ./capabilities

Гэсэн хэдий ч, бид root эрх олгохгүй байгаа тул энэ код нь портыг холбоход алдаа гаргах болно:

2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1

capsh (бүрхүүл менежер) нь тодорхой багц чадамж бүхий бүрхүүлийг ажиллуулдаг хэрэгсэл юм.

Энэ тохиолдолд аль хэдийн дурьдсанчлан, үндсэн эрхийг бүрэн олгохын оронд та програмд ​​байгаа бусад бүх зүйлийн хамт cap_net_bind_service боломжийг олгож давуу эрхтэй порт холболтыг идэвхжүүлж болно. Үүнийг хийхийн тулд бид програмаа том үсгээр хавсаргаж болно:

# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' 
   --keep=1 --user="nobody" 
   --addamb=cap_net_bind_service -- -c "./capabilities"

Энэ багийг жаахан ойлгоцгооё.

  • capsh - capsh-г бүрхүүл болгон ашиглах.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - бид хэрэглэгчийг өөрчлөх шаардлагатай байгаа тул (бид root эрхээр ажиллуулахыг хүсэхгүй байна) бид cap_net_bind_service болон хэрэглэгчийн ID-г өөрчлөх боломжийг зааж өгнө. root-ийг хэнд ч өгөхгүй, тухайлбал cap_setuid болон cap_setgid.
  • —keep=1 — бид үндсэн бүртгэлээс шилжих үед суулгасан боломжуудыг хадгалахыг хүсэж байна.
  • —user=“nobody” — програмыг ажиллуулж буй эцсийн хэрэглэгч нь хэн ч биш болно.
  • —addamb=cap_net_bind_service — root горимоос шилжсэний дараа холбогдох боломжуудыг арилгахыг тохируулна.
  • - -c "./capabilities" - зүгээр л програмыг ажиллуул.

Холбогдсон чадварууд нь одоогийн программ нь execve() ашиглан тэдгээрийг гүйцэтгэх үед хүүхэд программуудад өвлөгддөг тусгай төрлийн чадвар юм. Зөвхөн холбогдохыг зөвшөөрсөн чадварууд буюу өөрөөр хэлбэл орчны чадавхи гэж өвлөгдөж болно.

--cas сонголтонд чадамжийг зааж өгсний дараа +eip гэж юу гэсэн үг болохыг та гайхаж байгаа байх. Эдгээр тугуудыг дараах чадварыг тодорхойлоход ашигладаг.

-идэвхжүүлсэн байх ёстой (p);

-ашиглах боломжтой (e);

-хүүхдийн процессоор өвлөгдөж болно (i).

Бид cap_net_bind_service-г ашиглахыг хүсч байгаа тул үүнийг e flag ашиглан хийх хэрэгтэй. Дараа нь бид бүрхүүлийг тушаалаар эхлүүлнэ. Энэ нь боломжуудыг хоёртын хувилбараар ажиллуулах бөгөөд бид үүнийг i тугаар тэмдэглэх хэрэгтэй. Эцэст нь, бид функцийг идэвхжүүлэхийг хүсч байна (бид үүнийг UID-г өөрчлөхгүйгээр хийсэн) p. Энэ нь cap_net_bind_service+eip шиг харагдаж байна.

Та үр дүнг ss ашиглан шалгаж болно. Гаралтыг хуудсанд оруулахын тулд бага зэрэг богиносгоё, гэхдээ энэ нь холбогдох порт болон хэрэглэгчийн ID-г 0-ээс өөр харуулах болно, энэ тохиолдолд 65:

# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0

Энэ жишээнд бид capsh ашигласан, гэхдээ та libcap ашиглан бүрхүүл бичиж болно. Дэлгэрэнгүй мэдээллийг man 3 libcap-ээс үзнэ үү.

Програм бичихдээ хөгжүүлэгч програмыг ажиллуулах явцад шаардлагатай бүх функцийг урьдчилан мэддэггүй; Түүнчлэн, эдгээр функцууд шинэ хувилбаруудад өөрчлөгдөж магадгүй юм.

Манай программын боломжуудыг илүү сайн ойлгохын тулд бид cap_capable цөмийн функцийн kprobe-г тохируулдаг BCC чадвартай хэрэглүүрийг авч болно.

/usr/share/bcc/tools/capable
TIME      UID  PID   TID   COMM               CAP    NAME           AUDIT
10:12:53 0 424     424     systemd-udevd 12 CAP_NET_ADMIN         1
10:12:57 0 1103   1101   timesync        25 CAP_SYS_TIME         1
10:12:57 0 19545 19545 capabilities       10 CAP_NET_BIND_SERVICE 1

cap_capable цөмийн функцэд bpftrace-г нэг давхаргын kprobe ашиглан бид ижил зүйлд хүрч чадна:

bpftrace -e 
   'kprobe:cap_capable {
      time("%H:%M:%S ");
      printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
    }' 
    | grep -i capabilities

Хэрэв kprobe-ийн дараа манай програмын боломжууд идэвхжсэн бол энэ нь дараахтай адил зүйлийг гаргах болно:

12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1

Тав дахь багана нь үйл явцад шаардлагатай чадамжууд бөгөөд энэ гаралт нь аудитын бус үйл явдлуудыг агуулдаг тул бид аудитын бус бүх шалгалтыг, эцэст нь аудитын туг (гаралтын хамгийн сүүлд) 1 гэж тохируулсан шаардлагатай чадамжийг хардаг. Бидний сонирхож буй нэг нь CAP_NET_BIND_SERVICE бөгөөд энэ нь 10 танигчтай include/uapi/linux/ability.h файлын цөмийн эх кодын тогтмол гэж тодорхойлогддог:

/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">

Ихэнхдээ runC эсвэл Docker зэрэг контейнеруудад давуу эрхгүй горимд ажиллах боломжийг олгохын тулд тэдгээрийг ажиллуулах үед идэвхжүүлдэг боловч ихэнх програмуудыг ажиллуулахад шаардлагатай чадамжийг л зөвшөөрдөг. Аппликешн нь тодорхой боломжуудыг шаарддаг бол Docker үүнийг --cap-add ашиглан хангаж чадна:

docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummy

Энэ тушаал нь контейнерт CAP_NET_ADMIN чадамжийг өгөх бөгөөд дамми0 интерфэйсийг нэмэхийн тулд сүлжээний холбоосыг тохируулах боломжийг олгоно.

Дараагийн хэсэгт шүүлтүүр хийх гэх мэт функцуудыг хэрхэн ашиглахыг харуулсан боловч өөр техник ашиглан бид өөрсдийн шүүлтүүрийг программчлан хэрэгжүүлэх боломжийг олгодог.

Seccomp

Seccomp нь Secure Computing гэсэн үг бөгөөд Линуксийн цөмд хэрэгжсэн хамгаалалтын давхарга бөгөөд хөгжүүлэгчдэд тодорхой системийн дуудлагыг шүүх боломжийг олгодог. Хэдийгээр Seccomp нь Линукстэй харьцуулах боломжтой боловч зарим системийн дуудлагыг удирдах чадвар нь тэдэнтэй харьцуулахад илүү уян хатан болгодог.

Seccomp болон Линуксийн функцууд нь бие биенээсээ үл хамаарах зүйл биш бөгөөд ихэвчлэн хоёулангийнх нь ашиг тусыг авахын тулд хамтдаа ашиглагддаг. Жишээлбэл, та процесст CAP_NET_ADMIN чадамж өгөхийг хүсэж болох боловч сокет холболтыг зөвшөөрөхгүй, системийн дуудлагыг хүлээн авах, хүлээн авах4.

Seccomp шүүлтүүрийн арга нь SECCOMP_MODE_FILTER горимд ажилладаг BPF шүүлтүүр дээр суурилдаг бөгөөд системийн дуудлагын шүүлтүүрийг пакетуудын адилаар гүйцэтгэдэг.

Seccomp шүүлтүүрийг PR_SET_SECCOMP үйлдлээр prctl ашиглан ачаалдаг. Эдгээр шүүлтүүрүүд нь seccomp_data бүтцээр илэрхийлэгдсэн Seccomp пакет тус бүрээр гүйцэтгэгддэг BPF програмын хэлбэрийг авдаг. Энэ бүтэц нь лавлагааны архитектур, системийн дуудлагын үед процессорын зааврын заагч, uint64 хэлбэрээр илэрхийлэгдсэн дээд тал нь зургаан системийн дуудлагын аргументуудыг агуулдаг.

seccomp_data бүтэц нь linux/seccomp.h файл дахь цөмийн эх кодоос иймэрхүү харагдаж байна:

struct seccomp_data {
int nr;
      __u32 arch;
      __u64 instruction_pointer;
      __u64 args[6];
};

Энэ бүтцээс харахад бид системийн дуудлага, түүний аргументууд эсвэл хоёулангийнх нь хослолоор шүүж болно.

Seccomp пакет бүрийг хүлээн авсны дараа шүүлтүүр эцсийн шийдвэр гаргахын тулд боловсруулалт хийж, дараа нь юу хийхийг цөмд хэлэх ёстой. Эцсийн шийдвэрийг буцаах утгуудын аль нэгээр (статусын код) илэрхийлнэ.

- SECCOMP_RET_KILL_PROCESS - үүнээс болж гүйцэтгэгдэхгүй байгаа системийн дуудлагыг шүүсний дараа бүх процессыг шууд устгадаг.

- SECCOMP_RET_KILL_THREAD - үүнээс болж гүйцэтгэгдэхгүй байгаа системийн дуудлагыг шүүсний дараа одоогийн хэлхээг шууд зогсооно.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD-д зориулсан бусад нэр.

- SECCOMP_RET_TRAP - системийн дуудлага хийхийг хориглосон бөгөөд SIGSYS (Муу системийн дуудлага) дохиог түүнийг дуудаж буй даалгавар руу илгээдэг.

- SECCOMP_RET_ERRNO - Системийн дуудлагыг гүйцэтгээгүй бөгөөд SECCOMP_RET_DATA шүүлтүүрийн буцах утгын нэг хэсэг нь хэрэглэгчийн орон зайд errno утга хэлбэрээр шилждэг. Алдааны шалтгаанаас хамааран өөр өөр алдааны утгыг буцаана. Алдааны дугааруудын жагсаалтыг дараагийн хэсэгт өгсөн болно.

- SECCOMP_RET_TRACE - Энэ үйл явцыг харж, хянахын тулд системийн дуудлагыг гүйцэтгэх үед таслан зогсоохын тулд - PTRACE_O_TRACESECCOMP-г ашиглан ptrace tracer-д мэдэгдэнэ. Хэрэв трекер холбогдоогүй бол алдаа гарч, errno-г -ENOSYS гэж тохируулсан бөгөөд системийн дуудлагыг гүйцэтгээгүй болно.

- SECCOMP_RET_LOG - системийн дуудлагыг шийдэж, бүртгэсэн.

- SECCOMP_RET_ALLOW - системийн дуудлагыг зүгээр л зөвшөөрдөг.

ptrace нь процессын гүйцэтгэлийг хянах, хянах чадвартай, tracee гэж нэрлэгддэг процесст мөрдөх механизмыг хэрэгжүүлэх системийн дуудлага юм. Trace программ нь гүйцэтгэлд үр дүнтэй нөлөөлж, tracee-ийн санах ойн бүртгэлийг өөрчлөх боломжтой. Seccomp контекстэд ptrace нь SECCOMP_RET_TRACE төлөвийн кодоор өдөөгдөж байгаа үед ашиглагддаг тул тракер нь системийн дуудлагыг гүйцэтгэхээс сэргийлж, өөрийн логикийг хэрэгжүүлэх боломжтой.

Seccomp алдаа

Заримдаа Seccomp-тэй ажиллах явцад та SECCOMP_RET_ERRNO төрлийн буцаах утгаараа тодорхойлогддог янз бүрийн алдаатай тулгарах болно. Алдааг мэдээлэхийн тулд seccomp системийн дуудлага 1-ийн оронд -0-ийг буцаана.

Дараахь алдаа гарч болзошгүй.

- EACCESS - Дуудлага хийгч нь системийн дуудлага хийх эрхгүй. Энэ нь ихэвчлэн CAP_SYS_ADMIN эрхгүй эсвэл prctl ашиглан шинэ_захиа тохируулаагүйгээс болдог (бид энэ талаар дараа ярих болно);

— EFAULT — дамжуулсан аргументууд (seccomp_data бүтэц дэх аргументууд) хүчинтэй хаяггүй;

— EINVAL — энд дөрвөн шалтгаан байж болно:

-хүссэн үйлдэл тодорхойгүй эсвэл одоогийн тохиргоонд цөм дэмжигдээгүй;

-заасан тугнууд хүссэн үйлдэлд хүчингүй;

-үйл ажиллагаа нь BPF_ABS-г агуулдаг боловч заасан офсеттэй холбоотой асуудлууд байгаа бөгөөд энэ нь seccomp_data бүтцийн хэмжээнээс хэтэрч болзошгүй;

-шүүлтүүрт дамжуулсан зааврын тоо дээд хэмжээнээс хэтэрсэн;

— ENOMEM — програмыг гүйцэтгэхэд хангалттай санах ой байхгүй;

- EOPNOTSUPP - үйл ажиллагаа нь SECCOMP_GET_ACTION_AVAIL-ийн тусламжтайгаар үйлдлийг хийх боломжтой гэж заасан боловч цөм нь аргумент дахь буцаалтыг дэмждэггүй;

— ESRCH — өөр урсгалыг синхрончлоход асуудал гарсан;

- ENOSYS - SECCOMP_RET_TRACE үйлдэлд хавсаргасан мөр байхгүй байна.

prctl нь хэрэглэгчийн орон зайн программ нь байтын хэмжээ, урсгалын нэр, аюулгүй тооцооллын горим (Seccomp), эрх, Perf үйл явдал гэх мэт үйл явцын тодорхой талуудыг удирдах (тогтоох, авах) боломжийг олгодог системийн дуудлага юм.

Seccomp нь танд хамгаалагдсан хязгаарлагдмал орчинд ашиглах технологи мэт санагдаж болох ч тийм биш юм. Seccomp нь хэрэглэгчдэд хамгаалагдсан хязгаарлагдмал орчинд ажиллах механизмыг хөгжүүлэх боломжийг олгодог хэрэгсэл юм. Одоо Seccomp системийн дуудлагын дагуу шууд дуудагдсан шүүлтүүрийг ашиглан хэрэглэгчийн харилцан үйлчлэлийн программуудыг хэрхэн үүсгэдгийг харцгаая.

BPF Seccomp шүүлтүүрийн жишээ

Энд бид өмнө нь хэлэлцсэн хоёр үйлдлийг хэрхэн хослуулахыг харуулах болно, тухайлбал:

— бид Seccomp BPF програмыг бичих бөгөөд гаргасан шийдвэрээс хамааран өөр өөр буцах код бүхий шүүлтүүр болгон ашиглах болно;

— prctl ашиглан шүүлтүүрийг ачаална уу.

Юуны өмнө танд стандарт номын сан болон Линуксийн цөмийн толгой хэрэгтэй болно:

#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>

Энэ жишээг оролдохын өмнө бид цөмийг CONFIG_SECCOMP_FILTER болон y гэж тохируулсан CONFIG_SECCOMP_FILTER хөрвүүлсэн эсэхийг шалгах ёстой. Ажлын машин дээр та үүнийг дараах байдлаар шалгаж болно.

cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP

Кодын үлдсэн хэсэг нь хоёр хэсгээс бүрдсэн install_filter функц юм. Эхний хэсэг нь бидний BPF шүүлтүүрийн заавруудын жагсаалтыг агуулна.

static int install_filter(int nr, int arch, int error) {
  struct sock_filter filter[] = {
    BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
    BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
  };

Зааврыг linux/filter.h файлд тодорхойлсон BPF_STMT болон BPF_JUMP макро ашиглан тохируулна.
Зааврыг авч үзье.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - систем нь BPF_LD-ээс BPF_W үгийн хэлбэрээр ачаалагдаж, хуримтлагддаг, багц өгөгдөл нь BPF_ABS тогтмол офсет дээр байрладаг.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - BPF_JEQ ашиглан аккумляторын тогтмол BPF_K дахь архитектурын утга arch-тай тэнцүү эсэхийг шалгана. Хэрэв тийм бол офсет 0 дээр дараагийн заавар руу шилжих, үгүй ​​бол офсет 3 дээр үсэрч (энэ тохиолдолд) arch таарахгүй учир алдаа гаргана.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - BPF_LD-ээс BPF_ABS-ийн тогтмол офсетэд агуулагдах системийн дуудлагын дугаар болох BPF_W үгийн хэлбэрээр ачаалж, хуримтлагддаг.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — системийн дуудлагын дугаарыг nr хувьсагчийн утгатай харьцуулна. Хэрэв тэдгээр нь тэнцүү бол дараагийн заавар руу шилжиж, системийн дуудлагыг идэвхгүй болгоно, үгүй ​​бол SECCOMP_RET_ALLOW-р системийн дуудлагыг зөвшөөрнө.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (алдаа & SECCOMP_RET_DATA)) - програмыг BPF_RET-ээр зогсоож, үр дүнд нь err хувьсагчийн дугаартай SECCOMP_RET_ERRNO алдааг үүсгэдэг.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - програмыг BPF_RET-ээр дуусгаж, SECCOMP_RET_ALLOW ашиглан системийн дуудлагыг гүйцэтгэх боломжийг олгодог.

SECCOMP бол CBPF
Та яагаад эмхэтгэсэн ELF объект эсвэл JIT хөрвүүлсэн C програмын оронд зааврын жагсаалтыг ашигладаг вэ гэж гайхаж магадгүй юм.

Үүнд хоёр шалтгаан бий.

• Нэгдүгээрт, Seccomp нь eBPF биш cBPF (сонгодог BPF) ашигладаг бөгөөд энэ нь: энэ нь ямар ч бүртгэлгүй, зөвхөн хамгийн сүүлийн тооцооллын үр дүнг хадгалах аккумлятор гэдгийг жишээнээс харж болно.

• Хоёрдугаарт, Seccomp нь BPF зааврын массив руу заагчийг шууд хүлээн авдаг бөгөөд өөр юу ч биш. Бидний ашигласан макронууд нь эдгээр зааврыг программист ээлтэй байдлаар тодорхойлоход тусалдаг.

Хэрэв танд энэ чуулганыг ойлгоход илүү их тусламж хэрэгтэй бол ижил зүйлийг хийдэг псевдокодыг анхаарч үзээрэй.

if (arch != AUDIT_ARCH_X86_64) {
    return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
    return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;

Socket_filter бүтцэд шүүлтүүрийн кодыг тодорхойлсны дараа та код болон шүүлтүүрийн тооцоолсон уртыг агуулсан sock_fprog-г тодорхойлох хэрэгтэй. Энэхүү өгөгдлийн бүтэц нь дараа нь ажиллуулах процессыг зарлахад аргумент болгон шаардлагатай:

struct sock_fprog prog = {
   .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
   .filter = filter,
};

Install_filter функцэд хийх цорын ганц зүйл үлдсэн - програмыг өөрөө ачаалах! Үүнийг хийхийн тулд бид prctl-г ашиглан PR_SET_SECCOMP-ийг аюулгүй тооцоолох горимд оруулах сонголт болгон ашигладаг. Дараа нь бид sock_fprog төрлийн prog хувьсагчид агуулагдах SECCOMP_MODE_FILTER ашиглан шүүлтүүрийг ачаалах горимд заана:

  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
    perror("prctl(PR_SET_SECCOMP)");
    return 1;
  }
  return 0;
}

Эцэст нь бид install_filter функцийг ашиглаж болох боловч үүнээс өмнө бид prctl-г ашиглан одоогийн гүйцэтгэлд PR_SET_NO_NEW_PRIVS-ийг тохируулах хэрэгтэй бөгөөд ингэснээр хүүхдийн процессууд эцэг эхээсээ илүү эрх авах нөхцөл байдлаас зайлсхийх хэрэгтэй. Үүний тусламжтайгаар бид root эрхгүйгээр install_filter функцэд дараах prctl дуудлагуудыг хийж болно.

Одоо бид install_filter функцийг дуудаж болно. X86-64 архитектуртай холбоотой бүх бичих системийн дуудлагуудыг хааж, бүх оролдлогыг блоклох зөвшөөрлийг л өгцгөөе. Шүүлтүүрийг суулгасны дараа бид эхний аргументыг ашиглан гүйцэтгэлийг үргэлжлүүлнэ.

int main(int argc, char const *argv[]) {
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
   perror("prctl(NO_NEW_PRIVS)");
   return 1;
  }
   install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
  return system(argv[1]);
 }

Эхэлцгээе. Програмаа эмхэтгэхийн тулд бид clang эсвэл gcc-ийн аль нэгийг ашиглаж болно, ямар ч тохиолдолд энэ нь үндсэн.c файлыг тусгай сонголтгүйгээр эмхэтгэх болно.

clang main.c -o filter-write

Дээр дурдсанчлан бид програмын бүх оруулгыг хаасан. Үүнийг шалгахын тулд танд ямар нэгэн зүйл гаргадаг програм хэрэгтэй - ls сайн нэр дэвшигч юм шиг санагдаж байна. Тэр ихэвчлэн ингэж аашилдаг:

ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Apr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Apr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Apr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Apr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Apr 28 21:08 main.c

Гайхалтай! Манай боодолтой програмыг ашиглах нь дараах байдалтай байна: Бид шалгахыг хүссэн програмаа эхний аргумент болгон дамжуулдаг.

./filter-write "ls -la"

Гүйцэтгэх үед энэ програм нь бүрэн хоосон гаралтыг гаргадаг. Гэсэн хэдий ч бид юу болж байгааг харахын тулд strace-г ашиглаж болно:

strace -f ./filter-write "ls -la"

Ажлын үр дүн ихээхэн богиноссон боловч холбогдох хэсэг нь бидний тохируулсан EPERM алдаатай бичлэгүүдийг хаасан болохыг харуулж байна. Энэ нь програм бичих системийн дуудлагад хандаж чадахгүй учраас юу ч гаргадаггүй гэсэн үг юм.

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operation not permitted)

Одоо та Seccomp BPF хэрхэн ажилладагийг ойлгож, үүнтэй юу хийж болох талаар сайн ойлголттой болсон. Гэхдээ та бүрэн хүчийг ашиглахын тулд cBPF-ийн оронд eBPF-тэй ижил зүйлд хүрэхийг хүсэхгүй байна уу?

eBPF програмын талаар бодохдоо ихэнх хүмүүс үүнийг зүгээр л бичиж, администраторын эрхээр ачаалдаг гэж боддог. Энэ мэдэгдэл нь ерөнхийдөө үнэн боловч цөм нь янз бүрийн түвшинд eBPF объектуудыг хамгаалах механизмуудыг хэрэгжүүлдэг. Эдгээр механизмуудыг BPF LSM занга гэж нэрлэдэг.

BPF LSM урхи

Системийн үйл явдлын архитектураас хараат бус хяналтыг хангахын тулд LSM нь урхины тухай ойлголтыг хэрэгжүүлдэг. Дэгээ дуудлага нь техникийн хувьд системийн дуудлагатай төстэй боловч системээс хамааралгүй бөгөөд дэд бүтэцтэй нэгдсэн байдаг. LSM нь хийсвэрлэлийн давхарга нь янз бүрийн архитектурын системийн дуудлагуудтай ажиллахад тулгардаг асуудлаас зайлсхийхэд тусалдаг шинэ ойлголтыг өгдөг.

Үүнийг бичиж байх үед цөм нь BPF програмуудтай холбоотой долоон дэгээтэй бөгөөд SELinux нь тэдгээрийг хэрэгжүүлдэг цорын ганц суурилуулсан LSM юм.

Хавхуудын эх код нь include/linux/security.h файлын цөмийн модонд байрладаг:

extern int security_bpf(int cmd, union bpf_attr *attr, unsigned int size);
extern int security_bpf_map(struct bpf_map *map, fmode_t fmode);
extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_alloc(struct bpf_map *map);
extern void security_bpf_map_free(struct bpf_map *map);
extern int security_bpf_prog_alloc(struct bpf_prog_aux *aux);
extern void security_bpf_prog_free(struct bpf_prog_aux *aux);

Тэд тус бүрийг гүйцэтгэх янз бүрийн үе шатанд дуудах болно.

— security_bpf — гүйцэтгэсэн BPF системийн дуудлагын эхний шалгалтыг гүйцэтгэдэг;

- security_bpf_map - цөм газрын зургийн файлын тодорхойлогчийг буцаах үед шалгадаг;

- security_bpf_prog - цөм нь eBPF программын файлын тодорхойлогчийг буцаах үед шалгадаг;

— security_bpf_map_alloc — BPF газрын зураг доторх хамгаалалтын талбарыг эхлүүлсэн эсэхийг шалгана;

- security_bpf_map_free - BPF газрын зураг дотор хамгаалалтын талбарыг цэвэрлэсэн эсэхийг шалгана;

— security_bpf_prog_alloc — BPF программ дотор хамгаалалтын талбарыг эхлүүлсэн эсэхийг шалгана;

- security_bpf_prog_free - BPF программ доторх хамгаалалтын талбарыг цэвэрлэсэн эсэхийг шалгана.

Одоо, энэ бүхнийг хараад бид ойлгож байна: LSM BPF интерцепторуудын цаад санаа нь тэд eBPF объект бүрийг хамгаалалтаар хангаж, зөвхөн зохих давуу эрхтэй хүмүүс карт болон программ дээр үйл ажиллагаа явуулах боломжтой гэдгийг баталгаажуулах явдал юм.

Хураангуй

Аюулгүй байдал нь таны хамгаалахыг хүссэн бүх зүйлээ нэг дор хэрэгжүүлэх зүйл биш юм. Системийг янз бүрийн түвшинд, янз бүрийн аргаар хамгаалах чадвартай байх нь чухал юм. Итгэх эсэхээс үл хамааран системийг хамгаалах хамгийн сайн арга бол өөр өөр байрлалаас хамгаалах янз бүрийн түвшний хамгаалалтыг зохион байгуулах бөгөөд ингэснээр нэг түвшний хамгаалалтыг бууруулах нь бүхэл бүтэн системд нэвтрэх боломжийг олгодоггүй. Үндсэн хөгжүүлэгчид бидэнд янз бүрийн давхарга, мэдрэгчтэй цэгүүдийг өгөх гайхалтай ажлыг хийсэн. Бид танд давхаргууд гэж юу болох, тэдгээртэй ажиллахын тулд BPF програмуудыг хэрхэн ашиглах талаар сайн ойлголт өгсөн гэж найдаж байна.

Зохиогчдын тухай

Дэвид Калавера нь Netlify-ийн CTO юм. Тэрээр Docker-ийн дэмжлэгт ажиллаж, Runc, Go, BCC хэрэгслүүд болон бусад нээлттэй эхийн төслүүдийг хөгжүүлэхэд хувь нэмрээ оруулсан. Докерын төслүүд болон Docker залгаасын экосистемийг хөгжүүлснээрээ алдартай. Дэвид галын графикт маш их дуртай бөгөөд гүйцэтгэлийг оновчтой болгохыг үргэлж эрэлхийлдэг.

Лоренцо Фонтана Sysdig-ийн нээлттэй эх сурвалжийн багт ажилладаг бөгөөд тэрээр үндсэндээ цөмийн модуль болон eBPF-ээр дамжуулан контейнерийн ажиллах үеийн аюулгүй байдал, гажиг илрүүлэх боломжийг олгодог Cloud Native Computing Foundation төсөл болох Falco дээр төвлөрдөг. Тэрээр тархсан систем, програм хангамжаар тодорхойлсон сүлжээ, Линуксийн цөм, гүйцэтгэлийн шинжилгээнд дуртай.

» Номын талаарх дэлгэрэнгүй мэдээллийг эндээс авах боломжтой нийтлэгчийн вэбсайт
» Агуулга
» Ишлэл

Khabrozhiteley-ийн хувьд купон ашиглан 25% хөнгөлөлт - Linux

Номын цаасан хувилбарыг төлсний дараа цахим номыг цахим шуудангаар илгээнэ.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх