Записываемые и исполняемые области памяти

Вопрос или проблема

Я написал простой скрипт на Python, чтобы проверить /proc/{pid}/maps на наличие областей, которые одновременно доступны для записи и исполнения на моем компьютере. Удивительно, но некоторые из них обнаружились, все приватные и анонимные. Задумываюсь, зачем программе в наши дни может понадобиться область, доступная для записи и исполнения? Для чего они используются?

/proc/1286/maps
     ['/usr/lib/xorg/Xorg\x00:0\x00-seat\x00seat0\x00-auth\x00/var/run/lightdm/root/:0\x00-nolisten\x00tcp\x00vt7\x00-novtswitch\x00']
     7f5860c03000-7f5860c04000 rwxp 00000000 00:00 0
/proc/2659/maps
     ['xfwm4\x00--display\x00:0.0\x00--sm-client-id\x002c1781f72-47a5-494a-a3e7-32424563\x00']
     7ffb7d804000-7ffb7d805000 rwxp 00000000 00:00 0
/proc/404436/maps
     ['xfce4-terminal\x00--geometry=180x56-0-0\x00']
     7f44aa15a000-7f44aa18a000 rwxp 00000000 00:00 0
/proc/404436/maps
     ['xfce4-terminal\x00--geometry=180x56-0-0\x00']
     7f44aa19b000-7f44aa1fb000 rwxp 00000000 00:00 0
/proc/404436/maps
     ['xfce4-terminal\x00--geometry=180x56-0-0\x00']
     7f44aaa5c000-7f44aaa7c000 rwxp 00000000 00:00 0
/proc/404436/maps
     ['xfce4-terminal\x00--geometry=180x56-0-0\x00']
     7f44aabba000-7f44aabca000 rwxp 00000000 00:00 0
/proc/404436/maps
     ['xfce4-terminal\x00--geometry=180x56-0-0\x00']
     7f44ac736000-7f44ac766000 rwxp 00000000 00:00 0
/proc/407109/maps
     ['/usr/lib/firefox-esr/firefox-esr\x00-contentproc\x00-childID\x001\x00-isForBrowser\x00-prefsLen\x0037585\x00-prefMapSize\x00265304...']
     10737c04c000-10737c05c000 rwxp 00000000 00:00 0

Скрипт:

#!/usr/bin/env python3
import sys
import os
import re
import glob
from os.path import dirname, join

def main():
    map_files = list(filter(lambda f: re.match(r'^\d+$', f.split("https://unix.stackexchange.com/")[2]), glob.glob('/proc/*/maps')))
    for map_file in map_files:
        with open(map_file, 'r') as map_f:
            for line in map_f.readlines():  # для каждой отображаемой области
                [start, end, perms, offset, dev, inode, pathname] = parse_maps_line(line)
                if 'x' in perms and 'w' in perms:
                    print(map_file)
                    with open(join(dirname(map_file), 'cmdline'), 'r') as cmd_f:
                        print('\t', cmd_f.readlines())
                    print('\t', line.strip())



def parse_maps_line(line):
    ''' Формат файла:
    address           perms offset  dev   inode       pathname
    00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon
    '''
    [address, perms, offset, dev, inode, pathname] = re.split(r'\s+', line, 5)
    [start, end] = address.split('-')
    return [int(start, 16), int(end, 16), perms, int(offset, 16), dev, inode, pathname]


if __name__ == "__main__":
    main()

Системы, которые позволяют программирование на основе образов, где компилятор является частью образа, обычно требуют, чтобы некоторые области памяти были одновременно доступными для записи и исполнения, но это может быть не одновременно. Это необходимо для того, чтобы компилировать новый код и затем выполнять его. Если они компилируют только в байт-код, они могут обойтись без разрешения на выполнение, но высокопроизводительные системы, которые компилируют в нативный код, будут требовать этого.

Динамически подключаемые библиотеки могут нуждаться в таблице переходов, которая заполняется во время выполнения для указания на функции.

Наличие областей, которые одновременно доступны для записи и исполнения, требует тщательного программирования, так как процессоры часто имеют отдельные кэши инструкций и данных, которые могут не обновляться корректно аппаратной поддержкой. Это может быть более значительной проблемой, если в системе несколько процессоров.

Ответ или решение

В современном программировании и операционных системах управление памятью включает в себя множество аспектов, связанных с безопасностью и производительностью. Одним из таких аспектов является наличие регионов памяти, которые одновременно и записываемые, и исполняемые. На первый взгляд это может показаться странным, особенно учитывая современный подход к безопасности, который стремится свести к минимуму возможность выполнения случайного или вредоносного кода. Попробуем рассмотреть причину существования таких регионов и их применение.

### Теория

Во-первых, стоит отметить важность как записи, так и исполнения кода в определенных сценариях. Некоторые системы программирования реализуют подход, который называется “образ пpограммирования” (image-based programming). В таких системах компилятор встроен в сам image, что позволяет компилировать и исполнять код во время выполнения. Для этого необходимо, чтобы некоторые регионы памяти могли быть и записываемыми, и исполняемыми.

Вторая распространенная причина — механизмы, которые используются в динамических библиотеках. При динамической линковке может потребоваться таблица переходов (jump table), которая заполняется во время выполнения программы для правильного адресации функций. В таких случаях требуется регион памяти, который будет одновременно записываемым и исполняемым.

### Пример

Посмотрим на вывод Python-скрипта, часть которого вы представили. Некоторые процессы, такие как ‘Xorg’, ‘xfwm4’ и ‘firefox-esr’, используют такие регионы памяти. Это может быть вызвано необходимостью в реалокации исполняемых частей кода или же применением особых архитектурных подходов, где динамическая изменяемость кода является нормой.

### Применение

Однако, несмотря на вышесказанное, возможность исполнения записываемого кода несет в себе риски. Такие регионы могут быть целью атак, таких как внедрение и выполнение вредоносного кода. В связи с этим система должна предусматривать механизмы контроля и очистки данных кеша процессора, особенно в многопроцессорных системах.

Для повышения безопасности, многие современные ОС и среды исполнения используют такие подходы, как Data Execution Prevention (DEP) или Address Space Layout Randomization (ASLR), чтобы минимизировать риски. При проектировании систем и программ нужно тщательно оценивать необходимость использования записываемых и исполняемых регионов памяти, а также предусматривать дополнительные меры по защите.

Таким образом, наличие записываемых и исполняемых регионов памяти может быть обусловлено необходимостью в системах, где динамическое изменение кода является частью архитектуры. Однако это требует тщательной разработки и внедрения мер безопасности для предотвращения потенциальных уязвимостей.

Оцените материал
Добавить комментарий

Капча загружается...