Linux - жүйелік қоңыраулар. Linux жүйесінде жүйелік қоңыраулар
Бұл материал «System Administrator» журналында жарияланған Владимир Мешковтың аттас мақаласының модификациясы болып табылады.
Бұл материал Владимир Мешковтың «Жүйелік әкімші» журналындағы мақалаларының көшірмесі болып табылады. Бұл мақалаларды төмендегі сілтемелерден табуға болады. Сондай-ақ, бағдарламаның бастапқы мәтіндерінің кейбір мысалдары өзгертілді - жетілдірілді, аяқталды. (4.2-мысал айтарлықтай өзгертілген, себебі сәл басқаша жүйелік қоңырауды ұстау керек болды) URL мекенжайлары: http://www.samag.ru/img/uploaded/p.pdf http://www.samag.ru/img/ жүктеп салынған/a3.pdf
Сұрақтар бар? Сонда сіз осындасыз: [электрондық пошта қорғалған]
- 2. Жүктелетін ядро модулі
- 4. LKM негізіндегі жүйелік қоңырауларды ұстап алу мысалдары
- 4.1 Каталог құруды өшіру
1. Linux архитектурасының жалпы көрінісі
Ең жалпы көрініс жүйенің екі деңгейлі моделін көруге мүмкіндік береді. ядро<=>progs Орталықта (сол жақта) жүйенің ядросы орналасқан. Ядро қолданбалы бағдарламаларды архитектуралық мүмкіндіктерден оқшаулай отырып, компьютердің аппараттық құралдарымен тікелей әрекеттеседі. Ядрода қолданбалы бағдарламаларға ұсынылатын қызметтер жиынтығы бар. Ядро қызметтеріне енгізу-шығару операциялары (файлдарды ашу, оқу, жазу және басқару), процестерді құру және басқару, оларды синхрондау және процестер аралық байланыс кіреді. Барлық қолданбалар жүйелік қоңыраулар арқылы ядро қызметтерін сұрайды.Екінші деңгей қолданбалардан немесе тапсырмалардан тұрады, жүйенің функционалдығын анықтайтын жүйелік және Linux пайдаланушы интерфейсін қамтамасыз ететін қолданбалы. Дегенмен, қосымшалардың сыртқы гетерогенділігіне қарамастан, ядромен әрекеттесу схемалары бірдей.
Ядромен әрекеттесу стандартты жүйелік шақыру интерфейсі арқылы жүзеге асады. Жүйелік қоңырау интерфейсі ядро қызметтерінің жиынтығы болып табылады және қызметтерге сұраныстардың пішімін анықтайды. Процесс кәдімгі кітапхана функциясын шақыру сияқты көрінетін белгілі бір ядро процедурасына жүйелік қоңырау шалу арқылы қызметті сұрайды. Ядро процесс атынан сұранысты орындайды және қажетті деректерді процесске қайтарады.
Жоғарыда келтірілген мысалда бағдарлама файлды ашады, одан деректерді оқиды және файлды жабады. Бұл жағдайда файлды ашу (ашу), оқу (оқу) және жабу (жабу) операциясы тапсырманың сұранысы бойынша ядро арқылы орындалады, ал ашу (2), оқу (2) және жабу (2) ) функциялар жүйелік шақырулар болып табылады.
/* 1.0 көзі */ #include
- EAX регистріне – жүйелік шақырудың нөмірі. Сонымен, біздің жағдайда жүйенің қоңырау нөмірі 5 (__NR_open қараңыз).
- EBX регистріне – функцияның бірінші параметрі (open() үшін ол ашылатын файлдың атын қамтитын жолға көрсеткіш.
- ECX регистріне – екінші параметр (файлға қол жеткізу құқығы)
Дұрыс жолда екенімізге көз жеткізу үшін libc жүйелік кітапханасындағы open() функциясының кодын қарастырайық:
# gdb -q /lib/libc.so.6 (gdb) disas open Функция үшін ассемблер кодын тастаңыз: 0x000c8080
Енді жүйелік шақыру механизміне оралайық. Сонымен, ядро 0x80 үзу өңдеушісін - system_call функциясын шақырады. System_call SAVE_ALL макросы арқылы шақыру параметрлері бар регистрлердің көшірмелерін стекке итереді және шақыру пәрменімен қажетті жүйе функциясын шақырады. Жүйелік шақыруларды жүзеге асыратын ядро функцияларына көрсеткіштер кестесі sys_call_table массивінде орналасқан (arch/i386/kernel/entry.S файлын қараңыз). EAX регистрінде орналасқан жүйелік шақыру нөмірі осы массивтің индексі болып табылады. Осылайша, егер EAX құрамында 5 мәні болса, sys_open() ядро функциясы шақырылады. SAVE_ALL макросы не үшін қажет? Мұндағы түсініктеме өте қарапайым. Ядро жүйесінің барлық функциялары дерлік Си тілінде жазылғандықтан, олар өз параметрлерін стектен іздейді. Ал параметрлер SAVE_ALL көмегімен стекке итеріледі! Жүйелік шақырудың қайтару мәні EAX регистрінде сақталады.
Енді жүйелік қоңырауды қалай ұстау керектігін анықтайық. Бұл бізге жүктелетін ядро модульдерінің механизмі көмектеседі.
2. Жүктелетін ядро модулі
Loadable Kernel Module (LKM - Loadable Kernel Module) — ядро кеңістігінде жұмыс істейтін код. Негізгі мүмкіндік LKM - бұл мүмкіндік динамикалық жүктеужәне бүкіл жүйені қайта жүктемей немесе ядроны қайта компиляциялаусыз жүкті босатыңыз.Әрбір LKM екі негізгі функциядан тұрады (ең аз):
- модульді инициализациялау функциясы. LKM жадқа жүктелген кезде шақырылады: int init_module(void) ( ... )
- модульді босату функциясы: void cleanup_module(void) ( ... )
3. LKM негізіндегі жүйелік шақыруды ұстап алу алгоритмі
Жүйелік шақыруды тоқтататын модульді жүзеге асыру үшін ұстап алу алгоритмін анықтау қажет. Алгоритм келесідей:- бастапқы (түпнұсқа) қоңырауға көрсеткішті қалпына келтіруге болатын етіп сақтаңыз
- жаңа жүйелік шақыруды жүзеге асыратын функцияны жасаңыз
- sys_call_table жүйелік шақыру кестесіндегі қоңырауларды ауыстыру, яғни сәйкес көрсеткішті жаңа жүйелік қоңырауға орнату
- жұмыстың соңында (модуль түсірілгенде) бұрын сақталған көрсеткішті пайдаланып бастапқы жүйелік қоңырауды қалпына келтіріңіз
4. LKM негізіндегі жүйелік қоңырауларды ұстап алу мысалдары
4.1 Каталог құруды өшіру
Каталог жасалғанда, sys_mkdir ядросының функциясы шақырылады. Параметр құрылатын каталогтың атын қамтитын жол болып табылады. Сәйкес жүйелік шақыруды тоқтататын кодты қарастырыңыз. /* 4.1 көзі */ #include4.2 Каталогтағы файл жазбасын жасыру
Каталогтың мазмұнын оқу үшін қандай жүйелік шақыру жауапты екенін анықтайық. Мұны істеу үшін біз басқа жазамыз сынақ үзіндісі, ол ағымдағы каталогты оқиды: /* Source 4.2.1 */ #include- d_reclen - жазба өлшемі
- d_name - файл аты
5. /dev/kmem ядросының мекенжай кеңістігіне тікелей қатынау әдісі
Алдымен ядроның адрестік кеңістігіне тікелей қол жеткізу әдісімен ұстап алу қалай жүзеге асырылатынын теориялық тұрғыдан қарастырайық, содан кейін практикалық іске асыруға көшейік.Ядроның мекенжай кеңістігіне тікелей қол жеткізу /dev/kmem құрылғы файлы арқылы қамтамасыз етіледі. Бұл файл барлық қол жетімді виртуалды мекенжай кеңістігін, соның ішінде своп бөлімін (своп-аумақ) көрсетеді. Kmem файлымен жұмыс істеу үшін стандартты жүйе функциялары пайдаланылады - open(), read(), write(). /dev/kmem стандартты түрде ашылғанда, біз жүйедегі кез келген мекенжайға оны осы файлда офсет ретінде орнату арқылы сілтеме жасай аламыз. Бұл әдісті Сильвио Чезаре жасаған.
Жүйе функцияларына функция параметрлерін процессор регистрлеріне жүктеп, содан кейін бағдарламалық құралды үзу 0x80 шақыру арқылы қол жеткізуге болады. Осы үзілістің өңдеушісі, system_call функциясы, шақыру параметрлерін стекке итереді, sys_call_table кестесінен шақырылатын жүйе функциясының мекенжайын шығарып алады және басқаруды сол мекенжайға тасымалдайды.
Ядроның мекенжай кеңістігіне толық қол жеткізу арқылы біз жүйелік шақырулар кестесінің барлық мазмұнын ала аламыз, яғни. барлық жүйе функцияларының адрестері. Кез келген жүйелік қоңыраудың мекенжайын өзгерту арқылы біз оны ұстап аламыз. Бірақ ол үшін кестенің адресін немесе басқаша айтқанда, осы кесте орналасқан /dev/kmem файлындағы ығысуды білу керек.
sys_call_table кестесінің мекенжайын анықтау үшін алдымен жүйелік_шақыру функциясының мекенжайын есептеу керек. Бұл функция үзу өңдеушісі болғандықтан, қорғалған режимде үзілістердің қалай өңделетінін қарастырайық.
Нақты режимде үзуді тіркеу кезінде процессор үзіліс векторының кестесіне қол жеткізеді, ол әрқашан жадтың ең басында тұрады және үзіліс өңдеушілердің екі сөзден тұратын адрестерін қамтиды. Қорғалған режимде үзілістердің векторлық кестесінің аналогы қорғалған режим операциялық жүйесінде орналасқан үзу дескрипторының кестесі (IDT) болып табылады. Процессордың осы кестеге қол жеткізуі үшін оның мекенжайы үзіліс дескрипторы кестесі регистріне (IDTR) жүктелуі керек. IDT кестесінде үзу өңдегішінің дескрипторлары бар, олар, атап айтқанда, олардың адрестерін қамтиды. Бұл дескрипторлар шлюздер (қақпалар) деп аталады. Процессор үзуді тіркей отырып, оның нөмірі бойынша IDT шлюзін шығарып, өңдеушінің адресін анықтайды және оған басқаруды береді.
IDT кестесінен жүйелік_шақыру функциясының адресін есептеу үшін int $0x80 үзу қақпасын, ал одан сәйкес өңдеушінің адресін, яғни. жүйелік_шақыру функциясының мекенжайы. Жүйелік_шақыру функциясында жүйелік_шақыру_кестесіне шақыру пәрмені арқылы қол жеткізіледі<адрес_таблицы>(,%ең,4). /dev/kmem файлында осы пәрменнің операциялық кодын (қолтаңбасын) тапқаннан кейін біз жүйелік шақырулар кестесінің мекенжайын табамыз.
Операциялық кодты анықтау үшін отладчикті қолданып, system_call функциясын бөлшектейік:
# gdb -q /usr/src/linux/vmlinux (gdb) disas system_call System_call функциясына ассемблер кодының қоқысы: 0xc0194cbc
Ұстау операциясын орындайтын псевдокодты қарастырайық:
readaddr(eski_syscall, scr + SYS_CALL*4, 4); writeaddr(new_syscall, scr + SYS_CALL*4, 4); Readaddr функциясы жүйелік шақырулар кестесінен жүйелік шақыру мекенжайын оқиды және оны old_syscall айнымалысында сақтайды. sys_call_table кестесіндегі әрбір жазба 4 байт алады. Қажетті адрес /dev/kmem файлында sct + SYS_CALL*4 ығысуында орналасқан (мұнда sct — sys_call_table кестесінің мекенжайы, SYS_CALL — жүйелік шақырудың сериялық нөмірі). writeaddr функциясы SYS_CALL жүйелік шақыруының мекенжайын new_syscall функциясының мекенжайымен қайта жазады және SYS_CALL жүйелік шақыруына барлық шақыруларға осы функция қызмет көрсетеді.
Барлығы қарапайым, мақсат орындалған сияқты. Дегенмен, біз пайдаланушының мекенжай кеңістігінде жұмыс істеп жатқанымызды еске түсірейік. Егер біз осы мекенжай кеңістігіне жаңа жүйе функциясын орналастырсақ, онда бұл функцияны шақырған кезде біз әдемі қате туралы хабар аламыз. Бұдан шығатын қорытынды – ядроның адрестік кеңістігіне жаңа жүйелік шақыруды орналастыру керек. Ол үшін сізге қажет: ядро кеңістігінде жад блогын алу, осы блокта жаңа жүйелік шақыруды орналастыру.
kmalloc функциясын пайдаланып, ядро кеңістігінде жадты бөлуге болады. Бірақ сіз пайдаланушының мекенжай кеңістігінен ядро функциясын тікелей шақыра алмайсыз, сондықтан біз келесі алгоритмді қолданамыз:
- sys_call_table кестесінің мекенжайын біле отырып, біз кейбір жүйелік қоңыраудың мекенжайын аламыз (мысалы, sys_mkdir)
- kmalloc функциясына шақыруды орындайтын функцияны анықтаймыз. Бұл функция ядроның мекенжай кеңістігіндегі жад блогына көрсеткішті қайтарады. Бұл функцияны get_kmalloc деп атаймыз
- sys_mkdir жүйелік шақыруының бірінші N байттарын сақтаңыз, мұнда N - get_kmalloc функциясының өлшемі
- get_kmalloc функциясымен sys_mkdir қоңырауының бірінші N байтының үстінен жазыңыз
- біз sys_mkdir жүйелік қоңырауына шақыруды орындаймыз, осылайша орындау үшін get_kmalloc функциясын іске қосамыз
- sys_mkdir жүйелік шақыруының бірінші N байттарын қалпына келтіріңіз
Бірақ бұл алгоритмді жүзеге асыру үшін бізге kmalloc функциясының мекенжайы қажет. Сіз оны бірнеше жолмен таба аласыз. Ең қарапайымы - бұл мекенжайды System.map файлынан оқу немесе оны gdb отладкасының көмегімен анықтау (басып шығару &kmalloc). Ядрода модуль қолдауы қосылған болса, kmalloc мекенжайын get_kernel_syms() функциясы арқылы анықтауға болады. Бұл опция одан әрі талқыланады. Егер ядро модульдері үшін қолдау болмаса, kmalloc функциясының мекенжайын kmalloc шақыру пәрменінің операциялық коды бойынша іздеу керек болады - sys_call_table кестесі үшін жасалғанға ұқсас.
kmalloc функциясы екі параметрді қабылдайды: сұралған жадтың өлшемі және GFP спецификаторы. Операциялық кодты табу үшін біз отладчикті қолданамыз және kmalloc функциясына шақыруды қамтитын кез келген ядро функциясын бөлшектейміз.
# gdb -q /usr/src/linux/vmlinux (gdb) disas inter_module_register Inter_module_register функциясы үшін ассемблер кодының қоқысы: 0xc01a57b4
Бұл теориялық есептеулерді аяқтайды және жоғарыда аталған әдістемені қолдана отырып, sys_mkdir жүйелік шақыруын тоқтатамыз.
6. /dev/kmem көмегімен ұстап алудың мысалы
/* 6.0 көзі */ #includeҚағаздың соңы/EOP
Барлық бөлімдердегі кодтың өнімділігі 2.4.22 ядросында тексерілді. Есепті дайындау кезінде сайт материалдары пайдаланылдыКөбінесе __NR_xxx нөмірлі жүйелік қоңырау коды анықталған /usr/include/asm/unistd.h, функциядағы Linux ядросының бастапқы кодынан табуға болады sys_xxx(). (i386 үшін қоңыраулар кестесін мына жерден табуға болады /usr/src/linux/arch/i386/kernel/entry.S.) Бұл ережеден көптеген ерекшеліктер бар, негізінен ескі жүйелік қоңыраулардың көпшілігі жаңаларына ауыстырылады және ешқандай жүйесіз. parisc, sparc, sparc64 және alpha сияқты меншікті эмуляция платформаларында көптеген қосымша жүйелік қоңыраулар бар; mips64-те 32-биттік жүйелік қоңыраулардың толық жиынтығы бар.
Уақыт өте қажет болған жағдайда кейбір жүйелік қоңыраулардың интерфейсіне өзгерістер енгізілді. Бұл өзгерістердің себептерінің бірі құрылымдардың өлшемін немесе жүйелік шақыруға берілетін скаляр мәндерді ұлғайту қажеттілігі болды. Осы өзгерістерге байланысты кейбір архитектураларда (мысалы, ескі 32-биттік i386-да) ұқсас жүйелік қоңыраулардың әртүрлі топтары пайда болды (мысалы, қысқарту(2) және қысқарту64(2)), олар бірдей тапсырмаларды орындайды, бірақ аргументтерінің өлшемі бойынша ерекшеленеді. (Айтылғандай, қолданбалар әсер етпейді: glibc орауыштары дұрыс жүйелік шақыруды іске қосу үшін біраз жұмыс жасайды және бұл ескі екілік файлдар үшін ABI үйлесімділігін қамтамасыз етеді.) Бірнеше нұсқасы бар жүйелік қоңыраулардың мысалдары:
*Қазіргі уақытта үшеуі бар әртүрлі нұсқалар стат(2): sys_stat() (орын __NR_oldstat), sys_newsstat() (орын __NR_stat) және sys_stat64() (орын __NR_stat64), соңғысы қазіргі уақытта қолданылуда. Осыған ұқсас жағдай lstat(2) және fstat(2). * Дәл осылай анықталған __NR_oldolduname, __NR_oldunameжәне __NR_nameқоңыраулар үшін sys_olduname(), sys_name() және sys_newuname(). * Linux 2.0 жаңа нұсқасы бар vm86(2), жаңа және ескі нұсқасыядролық процедуралар деп аталады sys_vm86old() және sys_vm86(). * Linux 2.4 жаңа нұсқасы бар алу шегі(2) ядролық процедуралардың жаңа және ескі нұсқалары деп аталады sys_old_getrlimit() (орын __NR_getrlimit) және sys_getrlimit() (орын __NR_ugetrlimit). * Linux 2.4 жүйесінде пайдаланушы мен топ идентификаторы өрісінің өлшемі 16 биттен 32 битке дейін ұлғайтылды. Бұл өзгерісті қолдау үшін бірнеше жүйелік қоңыраулар қосылды (мысалы, Чон32(2), getuid32(2), getgroups32(2), setresuid32(2)), бұрынғы атаулары бар, бірақ "32" жұрнағы жоқ бұрынғы қоңырауларды пайдаланудан бас тарту. * Linux 2.4 кіруге қолдауды қосты үлкен файлдар(өлшемдері мен ығысулары 32 битке сәйкес келмейтін) 32 биттік архитектуралардағы қолданбаларда. Бұл файл өлшемдерімен және ығысулармен жұмыс істейтін жүйелік қоңырауларға өзгертулерді талап етті. Келесі жүйелік қоңыраулар қосылды: fcntl64(2), getdents64(2), stat64(2), statfs64(2), қысқарту64(2) және олардың файл дескрипторларын немесе символдық сілтемелерді өңдейтін аналогтары. Бұл жүйелік шақырулар "stat" шақыруларын қоспағанда, бірдей аталатын, бірақ "64" жұрнағы жоқ ескі жүйе шақыруларын жояды.
Тек 64 биттік файлға қатынасы және 32 биттік UID/GID (мысалы, alpha, ia64, s390x, x86-64) бар жаңа платформаларда UID/GID және жүйенің бір ғана нұсқасы бар файлға қол жеткізу. *64 және *32 қоңыраулары бар платформаларда (әдетте 32-биттік платформалар), басқа нұсқалар ескірген.
* Қиындықтар rt_sig*нақты уақыттағы қосымша сигналдарды қолдау үшін 2.2 ядросына қосылған (қараңыз сигнал(7)). Бұл жүйелік шақырулар бірдей атпен, бірақ "rt_" префиксі жоқ ескі жүйе қоңырауларын ескіреді. * Жүйе қоңырауларында таңдаңыз(2) және mmap(2) i386 жүйесінде аргументтердің қалай жіберілгенін анықтауда қиындықтар тудыратын бес немесе одан да көп аргумент пайдаланылады. Нәтижесінде, басқа архитектураларда шақырылады sys_select() және sys_mmap() сәйкес келеді __NR_таңдаужәне __NR_mmmap, i386-де олар сәйкес келеді old_select() және ескі_мап() (аргументтер блогына көрсеткішті пайдаланатын процедуралар). Қазіргі уақытта бес аргументтен артық айту мәселесі жоқ және бар __NR__жаңалық таңдау, ол дәл сәйкес келеді sys_select() және сол жағдай __NR_mmap2.
Жүйелік қоңыраулар
Осы уақытқа дейін біз жасаған барлық бағдарламалар /proc файлдарын және құрылғы драйверлерін тіркеу үшін жақсы анықталған ядро механизмдерін пайдалану керек болды. Бұл құрылғы драйверін жазу сияқты ядро бағдарламашылары ұсынған нәрсені жасағыңыз келсе өте жақсы. Бірақ егер сіз сәнді нәрсе жасағыңыз келсе, жүйенің әрекетін қандай да бір жолмен өзгерткіңіз келсе ше?
Дәл осы жерде ядролық бағдарламалау қауіпті болады. Төмендегі мысалды жазу кезінде мен ашық жүйелік қоңырауды жойдым. Бұл ешбір файлды аша алмайтынымды, ешқандай бағдарламаны іске қоса алмайтынымды және жүйені өшіру пәрменімен өшіре алмайтынымды білдірді. Мен оны тоқтату үшін қуатты өшіруім керек. Бақытымызға орай, ешбір файл жойылмады. Ешбір файлды жоғалтпау үшін insmod және rmmod пәрмендерін шығармас бұрын синхрондауды орындаңыз.
/proc файлдары мен құрылғы файлдарын ұмытыңыз. Олар жай ғана ұсақ бөлшектер. Барлық процестер пайдаланатын нақты ядролық байланыс процесі жүйелік шақырулар болып табылады. Процесс ядродан қызметті сұрағанда (мысалы, файлды ашу, жаңа процесті бастау немесе қосымша жадты сұрау) бұл механизм пайдаланылады. Егер сіз ядроның әрекетін қызықты жолдармен өзгерткіңіз келсе, бұл дұрыс орын. Айтпақшы, бағдарлама қандай жүйе шақырғанын көргіңіз келсе, іске қосыңыз: strace
Жалпы, процесс ядроға қол жеткізе алмайды. Ол ядро жадына қол жеткізе алмайды және ядро функцияларын шақыра алмайды. Орталық процессордың аппараттық құралы осы жағдайды қамтамасыз етеді (белгілі бір себептермен ол «қорғалған режим» деп аталады). Жүйелік қоңыраулар осы жалпы ережеден ерекшелік болып табылады. Процесс регистрлерді сәйкес мәндермен толтырады, содан кейін арнайы нұсқауды шақырады ядродағы алдын ала анықталған орын (әрине, оны пайдаланушы процестері оқиды, бірақ олар қайта жазбайды.) Intel процессорларында бұл 0x80 үзіліс арқылы орындалады. Аппараттық құрал осы орынға өткеннен кейін жұмыс істемейтінін біледі. жылы шектеулі режимпайдаланушы. Оның орнына сіз өзек ретінде жұмыс жасайсыз операциялық жүйе, сондықтан сізге не істегіңіз келсе, жасауға рұқсат беріледі.
Ядродағы процесс өтуі мүмкін орын system_call деп аталады. Онда орналасқан процедура жүйенің қоңырау нөмірін тексереді, ол ядроға процесс нені қалайтынын нақты айтады. Содан кейін ол шақырылатын ядро функциясының мекенжайын табу үшін жүйелік шақыру кестесін (sys_call_table) іздейді. Содан кейін қажетті функция шақырылады және ол мәнді қайтарғаннан кейін жүйеде бірнеше тексерулер жүргізіледі. Содан кейін нәтиже процеске (немесе процесс тоқтатылған болса, басқа процеске) қайтарылады. Мұның бәрін жасайтын кодты көргіңіз келсе, ол бастапқы файл arch/< architecture >/kernel/entry.S , ENTRY(жүйелік_шақыру) жолынан кейін.
Сонымен, егер кейбір жүйелік шақырулардың жұмысын өзгерткіміз келсе, бірінші кезекте тиісті нәрсені орындау үшін өз функциямызды жазуымыз керек (әдетте кейбір кодты қосып, содан кейін бастапқы функцияны шақырамыз), содан кейін көрсеткішті өзгертіңіз. функциямызды көрсету үшін sys_call_table параметріне. Біз кейінірек жойылуы мүмкін болғандықтан және жүйені тұрақсыз күйде қалдырғымыз келмейтіндіктен, cleanup_module үшін кестені бастапқы күйіне қайтару маңызды.
Мұнда берілген бастапқы код осындай модульдің мысалы болып табылады. Біз кейбір пайдаланушыға «тыңшылық» жасағымыз келеді және кез келген уақытта басып шығару хабарламасын жібергіміз келеді берілген пайдаланушыфайлды ашады. Біз файлды ашық жүйе шақыруын our_sys_open деп аталатын өз функциямызбен ауыстырамыз. Бұл функция ағымдағы процестің uid идентификаторын (пайдаланушы идентификаторы) тексереді және ол біз тыңшылық жасап жатқан uid-ге тең болса, ашылатын файлдың атын көрсету үшін printk шақырады. Содан кейін ол файлды ашатын бірдей параметрлермен бастапқы ашық функцияны шақырады.
init_module функциясы sys_call_table ішіндегі сәйкес орынды өзгертеді және бастапқы көрсеткішті айнымалы мәнде сақтайды. Cleanup_module функциясы осы айнымалы мәнді барлығын қалыпты жағдайға қайтару үшін пайдаланады. Бұл тәсіл екі модульдің бір жүйелік шақыруды өзгерту мүмкіндігіне байланысты қауіпті. Бізде екі модуль бар деп елестетіңіз, А және В. А модулінің ашық жүйелік шақыруы A_open деп аталады, ал сол В модулінің шақыруы B_open деп аталады. Енді ядро инъекцияланған жүйе шақыруы A_open ауыстырылды, ол бастапқы sys_open қажет нәрсені орындаған кезде шақырады. Содан кейін, B ядроға кірістіреді және жүйелік шақыруды B_open деп ауыстырады, ол бастапқы жүйелік шақыру деп санайтын нәрсені шақырады, бірақ шын мәнінде A_open.
Енді, егер алдымен B жойылса, бәрі жақсы болады: бұл түпнұсқаны шақыратын A_open жүйесінде жүйелік қоңырауды қалпына келтіреді. Дегенмен, егер А жойылса, содан кейін В жойылса, жүйе құлайды. A жою жүйелік қоңырауды бастапқыға қалпына келтіреді, sys_open, В циклден қиып тастайды. Содан кейін, B жойылған кезде, ол жүйелік қоңырауды бастапқы деп санайтын нәрсені қалпына келтіреді.Шын мәнінде, қоңырау енді жадта жоқ A_open-ге бағытталады. Бір қарағанда, жүйенің біздің ашық функциямызға тең екендігін тексеру және егер солай болса, сол шақырудың мәнін өзгертпеу арқылы (B жойылған кезде жүйені өзгертпеуі үшін) осы мәселені шешуге болатын сияқтымыз. бірақ бұл әлі де ең нашар проблема деп атайды. A жойылған кезде, ол жүйе шақыруы B_open болып өзгертілгенін көреді, осылайша ол енді A_open мәнін көрсетпейді, сондықтан ол жадтан жойылмас бұрын көрсеткішті sys_open күйіне қалпына келтірмейді. Өкінішке орай, B_open әлі де жадта жоқ A_open қызметіне қоңырау шалуға тырысады, сондықтан B жойылмаса да, жүйе әлі де істен шығады.
Мен бұл мәселені болдырмаудың екі жолын көремін. Біріншіден: қоңырауды sys_open бастапқы мәніне қалпына келтіріңіз. Өкінішке орай, sys_open /proc/ksyms ішіндегі жүйелік ядролар кестесінің бөлігі емес, сондықтан оған қол жеткізе алмаймыз. Тағы бір шешім - модульді түсіруге жол бермеу үшін сілтеме санауышын пайдалану. Бұл кәдімгі модульдер үшін жақсы, бірақ «білім беру» модульдері үшін нашар.
/* syscall.c * * Жүйелік қоңыраудың «ұрлау» үлгісі */ /* Авторлық құқық (C) 1998-99, Ori Pomerantz */ /* Қажетті тақырып файлдары */ /* Ядро модульдеріндегі стандартты */ #include