Zynq 7000. Прикручиваем Wi-Fi модуль RTL8822CS с использованием SDIO через EMIO

Наконец-то пришла пора продолжить изучение возможностей платы Zynq QMTech и SoC XC7Z020. Следующая интересная задача, которую я для себя придумал в качестве обучающей — оснастить плату Wi-Fi модулем Realtek RTL8822CS и, если Wi-Fi модуль будет не нужен, а нужна будет ещё одна флешка — вторым портом для SD-карточки. Если интересны подробности того, как я это всё реализовал — добро пожаловать под кат. 

Важно! Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте, с чего можно начать, при изучении отладочных плат на базе Zynq. Я не являюсь профессиональным разработчиком под ПЛИС и SoC Zynq, не являюсь системным программистом под Linux и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…

Сформулируем цель и задачи

Итак, у нас есть плата, описанная в этой (https://habr.com/ru/post/559946/) статье и есть желание прикрутить к ней Wi-Fi модуль Realtek RTL8822CS. Модуль, разумеется, должен корректно инициализироваться в ОС Linux и обеспечить пропускную способность до 150 Мбит\с (с чем связано такое ограничение  и именно этой цифрой — ниже). В качестве альтернативного варианта для использования выведенного SDIO — необходимо организовать второй порт для подключения SD-карт.

Исходя из этого можно сформировать несколько задач, решив которые мы можем достигнуть цели:

  1. Проверить возможность выноса линий SDIO через EMIO (т.е. через пины PL-части Zynq) и подготовить bitstream-файл;

  2. Проверить работоспособность SDIO через EMIO в приложении baremetal с использованием microSD-карточки;

  3. Изготовить мезонинные платы для Wi-Fi модуля и смонтировать на них компоненты;

  4. Подготовить First Stage Bootloader, Device Tree Source, U-Boot, Linux Kernel в нужной конфигурации для запуска Wi-Fi модуля;

  5. Подготовить модуль ядра для работы Wi-Fi модуля, а именно модуль cfg80211, который является линуксовым API для 802.11 устройств;

  6. Подготовить модуль ядра 88x2cs, который является драйвером для модуля Wi-Fi RTL8822CS который работает поверх SDIO-шины;

  7. Проверить работоспособность сети, сделать сканы эфира, посмотреть рабочие уровни, сделать замеры скорости downstream/upstream-трафиком через iPerf;

Итак. Перейдем к реализации намеченного, в целом, не выглядит сильно сложно =)

Настройка проекта в Vivado и конфигурация PL

В этой главе я кратко (без скриншотов) опишу процедуру создания нового проекта с акцентами на важных моментах. Если необходима инструкция с картинками — можно обратиться к моим предыдущим статьям. 

Открываем Vivado и создаем проект через кнопку Create Project. Откроется мастер настройки нового проекта, быстро пройдемся по шагам. 

  1. Даём имя проекту, например SDIO_EMIO и указываем папку сохранения;

  2. Указываем, что это RTL Project, оставляем включенной галочку Do not specify sources at this time;

  3. Находим модель SoC использованного на плате. В моем случае это xc7z020clg400-1;

  4. Нажимаем Finish.

В левом меню IP Integrator нажимаем Create Block Design и создаем новый дизайн. Назовём его для простоты block_design.

Добавляем примитив процессорной системы Zynq на диаграмму через нажатие правой кнопкой Add IP — ZYNQ7 Processing System. Сразу же выполним предложенные действия от автоматизатора, через команду на зеленом поле Run Block Automation и откроем настройки процессорной системы. 

Я заранее сохранил шаблон настроек PS, чтобы в каждом проекте не прописывать всё заново. Взять его можно тут (https://github.com/megalloid/zynq_qmtech/blob/main/ZynqQMTech.tcl). Через меню Presets — Apply Configuration применяем его и сразу переходим в меню Peripherial I/O Pins.

Ставим галочку возле меню SD 1 и проверяем, что выбран вариант подключения через EMIO:

Остальные настройки я оставил без изменений, в т.ч. частоты тактирования SDIO, PL. Получаем следующую картину (если развернуть группу сигнальных линий SDIO_1):

В данном случае нас интересуют линии CLK, CMD и 4 линии DATA, что в общем-то является стандартным набором для SDIO-шины. Но вот незадача, каждая из линий организована тремя отдельными сигналами. Для решения этой задачи нам необходимо руками создать буфер IOBUF, который будет управляться через активный пин T с тремя состояниями и разрулит Input, Output сигналы как нам нужно для использования его с двунаправленным сигналом I/O. Выглядит он следующим образом:

Т.к. в стандартной библиотеке графических примитивов он отсутствует — нужно будет создать свой IP-модуль и добавить его на схему. Для каждого из сигнала он будет свой.  

Через меню Sources — Add Sources (Alt + A) добавляем Design Source — Create File, выбираем тип файла Verilog и назовём его iobuf_cmd.

Запишем следующее содержимое:

module iobuf_cmd (     input iobuf_i,     // Вход буфера     input iobuf_t,     // Входной сигнал с тремя состояниями, high=input, low=output     output iobuf_o,    // Выход буфера     inout  iobuf_io     // Двунаправленный порт (подключается напрямую к top-level порту) );  IOBUF #(     .DRIVE(12),                 // Указывается Drive Strength для сигналов     .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"     .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала     .SLEW("FAST")               // Указывается slew rate ) IOBUF_inst (     .O(iobuf_o),      // Выход буфера     .IO(iobuf_io),    // Двунаправленный порт     .I(iobuf_i),      // Вход буфера     .T(iobuf_t)       // Входной сигнал с тремя состояниями ); endmodule 

То же самое сделаем для сигнала CLK, только файл назовём iobuf_clk. Запишем в него следующее содержание:

module iobuf_clk (     input iobuf_i,     // Вход буфера     input iobuf_t,     // Входной сигнал с тремя состояниями, high=input, low=output     output iobuf_o,    // Выход буфера     inout  iobuf_io    // Двунаправленный порт (подключается напрямую к top-level порту) );  IOBUF #(     .DRIVE(12),                 // Указывается Drive Strength для сигналов     .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"     .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала     .SLEW("FAST")               // Указывается slew rate ) IOBUF_inst (     .O(iobuf_o),      // Выход буфера     .IO(iobuf_io),    // Двунаправленный порт     .I(iobuf_i),      // Вход буфера     .T(iobuf_t)       // Входной сигнал с тремя состояниями ); endmodule 

С SDIO DATA немного сложнее т.к. она состоит из нескольких линий. Так же создадим файл iobuf_data и запишем в него следующее содержимое:

module iobuf_data (     input [3:0] iobuf_i,    // Вход буфера     input iobuf_t,          // Входной сигнал с тремя состояниями, high=input, low=output     output [3:0]iobuf_o,    // Выход буфера     inout [3:0] iobuf_io    // Двунаправленный порт (подключается напрямую к top-level порту) );      generate  genvar i ; for (i=0; i<4; i=i+1)      begin       IOBUF #(     .DRIVE(12),                 // Указывается Drive Strength для сигналов     .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"     .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала     .SLEW("FAST")               // Указывается slew rate )      IOBUF_inst (                 .O  (iobuf_o[i]),  // Выход буфера                 .IO (iobuf_io[i]), // Двунаправленный порт                 .I  (iobuf_i[i]),  // Вход буфера                 .T  (iobuf_t)      // Входной сигнал с тремя состояниями              );     end     endgenerate endmodule

Для каждой из линии создается свой экземпляр примитива IOBUF.

Поочередно размещаем добавленные модули на диаграмму через клик правой мышкой по Diagram — Add Module. Соединяем сигналы с нужными портами и получится следующее:

Хотелось бы обратить внимание на две важные вещи:

  1. На порт iobuf_t модуля для сигнала CLK я добавил Constant с шириной в 1 бит и со значением 0, т.к. управляющего T-пина для CLK не предусмотрено;

  2. Сделал порты iobuf_io как External и дал им соответствующие названия;

  3. Соединил порты Input с портами Output;

На текущем этапе можно запустить синтез и назначить выходы портов на реальные физические порты. Для этого нужно:

  1. Нажимаем на меню block_design правой кнопкой и выбираем команду Create HDL Wrapper

  2. В меню IP Integrator выбираем Generate Block Design

  3. Выбираем Synthesis Option — Global и нажимаем Apply, затем Generate

  4. Дожидаемся окончания генерации;

  5. Выбираем Run Synthesis для старта синтеза нашего дизайна.

  6. Дожидаемся окончания синтеза;

После окончания синтеза переходим в меню Open Synthesized Design и откроется меню Package с нижним меню I/O Ports. Далее необходимо:

  1. Отметить подходящие физические I/O пины

  2. Убедиться, что включена подтяжка PULLUP на всех пинах кроме CLK;

В моем случае получается следующее:

Сохраняем настройки, будет предложено создать файл с constraints. Называем его как удобно и нажимаем Ok. После нажимаем Generate bitstream, соглашаемся с тем, что синтез устарел и ждем окончания всех необходимых процедур до получения сообщения:

После этого можем экспортировать BSP в SDK и запустить проверку работоспособности SDIO в baremetal. Делается это через меню File — Export — Export Hardware, ставим Include bitstream. Нажимаем Ok и запускаем SDK через команду File — Launch SDK.

Проверка SDIO через microSD-карту в Baremetal

Прежде чем приступить к проверке необходимо подключить microSD-карту к пинам, которые мы размечали в следующем разделе. Смотрим в первую очередь на распиновку microSD-карты:

С пинами VDD (3.3V) и VSS (GND) всё в целом понятно. С другими пинами есть нюанс, для пина CMD и шины DATA требуется подтяжка к высокому уровню через 10 кОм резисторы. В моем случае, чтобы не городить навесным монтажом резисторы, я взял одну из плат, заготовленных для Wi-Fi модуля и запаял на него все что нужно:

Если посмотреть на принципиальную схему этой платы — все выглядит вот таким образом:

Питание подключено напрямую в соответствии с тем, как оно выведено на гребенку. Для удобства отладки логическим анализатором — я подпаивал МГТФ-проводки. 

После того, как все необходимые линии подключены — можно вернуться в SDK к созданию проекта. После запуска SDK обзываем проект SDIO_EMIO и нажимаем Next, выбираем шаблон проекта Hello World.

Открываем файл helloworld.c из древа проекта и пишем в него следующий код, который подключит нужные библиотеки и поможет нам быстро сделать проверку:

#include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "xsdps.h" #include "xparameters.h" #include "xil_types.h"  int main() { int Status;  static XSdPs SdInstance; XSdPs_Config *SdConfig;      init_platform();      xil_printf("\r\nSD Raw Read/ Write Test\r\n");  SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID);      if (NULL == SdConfig)     {       xil_printf("XSdPs_LookupConfig failed\r\n");       return XST_FAILURE;     }     else     {       xil_printf("XSdPs_LookupConfig ok \r\n");     }      Status = XSdPs_CfgInitialize(&SdInstance, SdConfig, SdConfig->BaseAddress);     if (Status != XST_SUCCESS)     {       xil_printf("XSdPs_CfgInitialize failed \r\n");       return XST_FAILURE;     }     else     {       xil_printf("XSdPs_CfgInitialize ok \r\n");     }      Status = XSdPs_CardInitialize(&SdInstance);     xil_printf("XSdPs_CardInitialize... \r\n");     if (Status != XST_SUCCESS)     {       xil_printf("XSdPs_CardInitialize failed \r\n");     }     else     {       xil_printf("XSdPs_CardInitialize ok \r\n");     } } 

Кликаем правой кнопкой по проекту SDIO_EMIO и выбираем Build Project. Смотрим, что проект собирается без проблем. Важный момент — выбрать правильный SDIO-контроллер в строке SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID);

Подключаем JTAG-отладчик и после сборки проекта заливаем bitstream-файл выполнив команду Xilinx — Program FPGA и дожидаемся сигнала FPGA DONE на плате. 

Открываем Serial Port, который принадлежит нашей плате. Например: minicom -D /dev/ttyUSB0. Узнать какой порт принадлежит плате можно запустив команду udevadm monitor | grep ttyUSB и выткнуть-воткнуть miniUSB кабель из платы. Там можно увидеть сообщения по которым можно понять какой у нас порт. Например:

megalloid@megalloid-lenovo:~$ udevadm monitor | grep ttyUSB KERNEL[191576.781021] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) KERNEL[191576.781124] unbind   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191576.781170] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV  [191576.796568] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) UDEV  [191576.804931] unbind   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV  [191576.811644] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191579.176521] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191579.177593] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) KERNEL[191579.177706] bind     /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV  [191579.217863] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV  [191579.225195] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) UDEV  [191579.229537] bind     /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) 

После запускаем проект также кликнув по корневому значку проекта выбрав меню Run As — Launch on hardware (System Debugger). В выводе в консоль мы должны увидеть следующее:

Что означают эти сообщения? Это значит, что драйвер успешно “достучался” до карточки и может спокойно начать с ней обмен. Значит наш SDIO-контроллер работает. Идём дальше.

Подробнее о standalone-драйвере можно почитать тут: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841881/Zynq+SD+Standalone+driver

Трассировка и изготовление мезонинных плат для Wi-Fi модуля

Следующим шагом нужно подготовить платы, на которые я запаяю радиомодуль, со всей необходимой обвязкой. В качестве первого радиомодуля был взят модуль от FN-Link с Realtek RTL8822CS чипом, модель модуля 6222B-SRC. Подробнее можно посмотреть тут: https://wikidevi.wi-cat.ru/Fn-Link_6222B-SRC , Datasheet: https://www.signal.com.tr/pdf/cat/6222B-SRC_03.pdf

Кратко скажу, что это простой MIMO 2×2 Dual Band модуль Wi-Fi с поддержкой 802.11ac и Bluetooth 4.2 или 5.0 в зависимости от модели. 

Так как модуль мы будем подключать к гребенке PLS на плате с шагом в 2.54мм. — было решено сделать такой же разъем и на мезонинной плате. 

Распиновка со стороны платы: 

Распиновка со стороны модуля:

Решено было вывести в т.ч. и Bluetooth для дальнейших экспериментов. В итоге общая обвязка модуля выглядит следующим образом:

Всё в целом достаточно примитивно и в соответствии с референсной схемой и небольшими изменениями, такими как пин включения\выключения питания модуля и прочих мелких доработок. Антенный тракт было решено вывести на u.FL-разъем, для удобства и возможности подключения любых подходящих для 2.4/5ГГц антенн. 

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

Результат трассировки в 2D:

Результат трассировки в 3D:

Вид сверху:

Вид снизу:

Вид платы после монтажа компонентов:

Т.к. плата изначально разрабатывалась не только для этой версии Wi-Fi модуля — остались не установленными некоторые компоненты. 

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

Небольшое дополнение проекта в Vivado

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

  1. Подать низкое напряжение на затвор P-канального ключа VT1, т.е. на линию WL_BT_ON, чтобы подать питание на радиомодуль;

  2. Подать высокое напряжение на ножку WL_REG_ON радиомодуля, чтобы запустить его;

Поэтому на этом этапе нам будет нужно слегка модернизировать проект и добавить 2 сигнала с AXI GPIO-контроллера и завести их на соответствующие ножки: 

  1. Для транзисторного ключа это ножка 11 на общей гребенке, что соответствует физическому пину W20;

  2. Для сигнала WL_REG_ON это 25-я ножка на той же гребенке, что соответствует в моем случае пину V17;

Как работать с AXI GPIO Я рассказывал в этой статье (https://habr.com/ru/post/565368/) и на деталях повторяться не буду. В нашем случае нужно выполнить лишь несколько шагов:

  1. Добавляем в дизайн IP-модуль AXI GPIO;

  2. Выполняем предложенные шаги автоматизации предложенные Vivado, отметив все галочки;

  3. Появится блок AXI GPIO, необходимо зайти в его опции и поставить галочку All Outputs, поставить ширину шины GPIO Width в значение 2 (нужно 2 сигнала) и в Default Output вписать 0x00000002, чтобы по умолчанию после загрузки bitstream-файла оба пина приняли необходимое значение;

  4. Называем External Interface от AXI GPIO как MGMT_GPIO и сохраняемся. Должно получиться следующее:

  1. Синтезируем дизайн, проверяем, что всё прошло успешно;

  2. Переходим в меню Open Synthesized Design и назначаем MGMT_GPIO[0] пин W20, а MGMT_GPIO[1] пин V17;

  3. Изменяем у этих двух пинов I/O Std на LVCMOS3.3 и сохраняем;

  4. Запускаем генерацию bitstream-файла через Generate bitstream;

  5. Экспортируем сгенерированный bitstream и всё что необходимо для дальнейших шагов через команду File — Export — Export Hardware

Все изменения, необходимые на этом этапе мы внесли. Переходим к подготовке всего необходимого для работы с радиомодулем в Linux;

Подготовка FSBL, Device Tree Sources, U-Boot

На этом этапе можно переходить к созданию загрузчиков, файлу описания железа и компиляции ядра Linux. Многие моменты и детали Я целенаправленно опущу т.к. уже рассказывал в предыдущей статье (https://habr.com/ru/post/565368/) а отличающимся моментам посвящу основную часть повествования. 

Первым этапом надо подготовить FSBL. Для этого нужно:

  1. В меню Xilinx SDK переходим в меню File — New — Application Project;

  2. Именуем его как FSBL;

  3. Нажимаем клавишу Next;

  4. Из заготовок выбираем Zynq FSBL;

  5. Дожидаемся окончания компиляции и проверяем, что появился файл FSBL.elf

Вторым этапом необходимо подготовить Device Tree Source/Blob файл который будет использован для сборки U-Boot. Для этого сделаем следующее:

  1. Добавляем BSP Repository из репозитория device-tree-xlnx. Для этого нужно выбрать меню Xilinx — Repositories и указать папку с device-tree-xlnx в секцию Global Repositories

  2. Добавляем BSP Project через меню File — New — Board Support Package, даём ему имя и выбираем внизу меню device_tree и в следующем меню нажимаем Ok, если не требуется внесения каких-либо изменений. 

  3. Выполним редактирование файла zynq-7000.dtsi и находим секцию описывающую SDIO1 в строке sdhci1: mmc@e0101000;

Дополняем секцию следующим содержанием (файл можно взять тут — https://github.com/megalloid/zynq_qmtech/blob/main/zynq-7000.dtsi) :

Переходим в папку с сгенерированными dts-файлами в системной консоли:

megalloid@megalloid-lenovo:~$ cd Zynq/Projects/SDIO_EMIO/SDIO_EMIO.sdk/device_tree/

Компонуем исходники:

gcc -I my_dts -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o system.dts system-top.dts

Компилируем в blob:

dtc -I dts -O dtb -o system.dtb system.dts

Всё. Device Tree готов.

Следующим этапом необходимо подготовить U-Boot для нашей платы. Нужно выполнить несколько простых шагов:

  1. Перейти в папку с U-Boot: cd u-boot-xlnx

  2. Задать переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm

  3. Очистить сырцы от предыдущих результатов компиляции и применить конфиг для Zynq по умолчанию: make distclean; make xilinx_zynq_virt_defconfig

  4. Положить в папку с U-Boot полученный в предыдущем шаге system.dtb в папку arch/arm/dts/ c именем zynq-qmtech.dtb

  5. Указываем какой Device Tree файл использовать: export DEVICE_TREE=»zynq-qmtech»

  6. Далее необходимо зайти в меню конфигуратора и изменить ряд опций: make menuconfig 

  7. Опция для того, чтобы активировать поддержку AXI GPIO, которая используется для включения питания модуля и подачи сигнала WL_REG_ON на радиомодуле: Device Drivers — GPIO Support — Xilinx GPIO Driver 

  8. Сохраняем конфиг (можно взять тут) в файл .config

  9. Запускаем сборку: make -j$(nproc) и дожидаемся окончания компиляции;

После этого можно скомпоновать FSBL, Bitstream-файл и U-Boot в один загрузочный файл BOOT.BIN. Подробное описание компоновки можно найти в моей прошлой статье (https://habr.com/ru/post/565368/). В целом надо выполнить несколько простых шагов:

  1. Найти файл в каталоге с проектом First stage bootloader. По умолчанию он находится в SDIO_EMIO/SDIO_EMIO.sdk/FSBL/Debug/FSBL.elf

  2. Найти Bitstream-файл для программируемой логики. Найти его можно в каталоге SDIO_EMIO/SDIO_EMIO.sdk/block_design_wrapper_hw_platform_0/block_design_wrapper.bit

  3. Найти бинарный файл U-Boot. Данный файл после компиляции лежит в папке с U-Boot: u-boot-xlnx/u-boot.elf

  4. Открываем в Xilinx SDK пункт меню Xilinx — Create Boot Image;

  5. Выбираем куда сохранить новый bif-файл, я его сохраняю в корневом каталоге проекта;

  6. Добавляем поочередно файлы FSBL.elf, block_design_wrapper.bit, u-boot.elf в секцию Boot Image Partitions;

  7. Нажимаем Create Image и видим как в каталоге с bif-файлом появляется BOOT.BIN;

Теперь необходимо залить этот файл на загрузочную SD-карту и можно попробовать загрузиться. Если мы видим приглашение от U-Boot и что светится светодиод FPGA DONE — значит мы всё выполнили правильно. 

Можно перейти к проверке того, проинициализирован ли AXI GPIO в U-Boot и проверим их. Для этого сначала выведем список устройств которые проинициализированы. Это можно сделать через команду dm tree.

Отлично. Видно, что у нас на борту есть два SDIO-контроллера, и два GPIO-контроллера. Попробуем изменить состояние ножки транзистора. Для этого нужно:

  1. Посмотреть какие адреса в GPIO доступны и какие отвечают за те, или иные ножки:  gpio status -a

  2. В конце вывода видно, что есть Bank GPIO с двумя ножками, которые соответствуют значению по умолчанию:

    1. Проверки ради можно изменить состояние ножки gpio@412000000 через команду: gpio toogle gpio@412000000 или gpio set gpio@412000000 / gpio clear gpio@412000000 для установки логической 1 или 0 соответственно

Так. Загрузчик подготовлен. Теперь можно переходить к подготовке ядра Linux и RootFS со всем необходимым для нас содержимым

Компиляция ядра Linux и подготовка RootFS с использованием Buildroot

Так же как и прошлых главах, для исключения необходимости подробного объяснения каждого шага, сошлюсь на предыдущую статью (https://habr.com/ru/post/565368/) как скомпилировать ядро Linux. Перейдем к выполнению шагов, необходимых для сборки ядра с необходимыми модулями и драйверами. Для этого нужно:

  1. Перейти в папку с клонированным репозиторием linux-xlnx;

  2. Обновить его (при необходимости): cd linux-xlnx; git fetch -p; git checkout master; git pull; 

  3. Экспортируем также переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm

  4. Делаем очистку от результатов предыдущих сборок: make distclean

  5. Загружаем дефолтный конфиг: make ARCH=arm xilinx_zynq_defconfig;

  6. Изменим дефолтный конфиг загрузив меню конфигурации: make ARCH=arm menuconfig

  7. Изменим опции конфигурации (можно взять тут https://github.com/megalloid/zynq_qmtech/blob/main/kernel.config) , устанавливаем опцию (проследим, что там вместо [M] в поле выбора стоит символ звездочки [*] : Networking Support — Wireless — cfg80211 

  8. Сохраняем конфиг в файл .config и выходим из меню конфигурации;

  9. Запускаем процесс компиляции: make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage -j8

  10. Компилируем модули ядра: make ARCH=arm -j8 modules

  11. Копируем uImage из каталога linux-xlnx/arch/arm/boot/ на загрузочную SD-карту;

Далее необходимо подготовить образ RootFS с всеми необходимым userspace-утилитами, типа wpa_supplicant, wpa_cli, etc. Собирать RootFS будем с помощью системы сборки buildroot, статью о которой Я писал до этого (https://habr.com/ru/post/567408/). Итак, перейдем к сборке, для этого нужно:

  1. Заходим в папку с buildroot: cd buildroot

  2. Обновляем его до последней версии: git fetch -p; git checkout master; git pull;

  3. Делаем очистку от результатов предыдущих сборок: make distclean

  4. Запускаем меню конфигурации: make -C /home/megalloid/buildroot O=$PWD ARCH=arm nconfig

    Изменяем опции конфигурации по списку ниже;

  5. Опция 1: Target options — Target Architecture — ARM (little endian)

  6. Опция 2: Target options — Target Architecture Variant — cortex-A9

  7. Опция 3: Target options — Enable NEON SIMD extension support — [*]

  8. Опция 4: Target options — Enable VFP extension support — [*]

  9. Опция 5: Target options — Target ABI — EABIhf

  10. Опция 6: Target options — Floating point strategy — NEON 

  11. Опция 7: Build options — Number of jobs to run simultaneously — 8 (по количеству ядер CPU)

  12. Опция 8: Toolchain — C library — glibc

  13. Опция 9: Toolchain — Kernel headers — Custom Git Repository 

  14. Опция 10: Toolchain — URL of custom repository —  </home/megalloid/linux-xlnx>

  15. Опция 11: Toolchain — Custom repository version — <хэш-сумма от git show-ref | grep master из папки linux-xlnx>

  16. Опция 12: Toolchain — Custom kernel headers series — 5.15.x

  17. Опция 13: Toolchain — Install glibc utilities — [*]

  18. Опция 14: Toolchain — GCC compiler Version — gcc 9.x

  19. Опция 15: Toolchain — Enable C++ support — [*]

  20. Опция 16: Toolchain — Enable compiler link-time-optimization support — [*]

  21. Опция 17: Toolchain — Enable compiler OpenMP support — [*]

  22. Опция 18: Target packages — Miscellaneous — haveged

  23. Опция 19: Target packages — Networking applications — dhcpd

  24. Опция 20: Target packages — Networking applications — dropbear

  25. Опция 21: Target packages — Networking applications — ifupdown scripts

  26. Опция 22: Target packages — Networking applications — hostapd

  27. Опция 23: Target packages — Networking applications — iperf

  28. Опция 24: Target packages — Networking applications — iperf3

  29. Опция 25: Target packages — Networking applications — fping

  30. Опция 26: Target packages — Networking applications — iw

  31. Опция 27: Target packages — Networking applications — iwd

  32. Опция 28: Target packages — Networking applications — wpa_supplicant

  33. Опция 28: Target packages — Networking applications — wireless tools

  34. Опция 29: Filesystem images — cpio the root filesystem — [*]

  35. Опция 30:  Filesystem images — Compression method — lzma

  36. Опция 31: Host Utilites — host dosfstools

  37. Опция 32: Host Utilites — host genimage

  38. Опция 33: Host Utilites — host mtools

  39. После этой длительной конфигурации выбираем Save и сохраняем конфиг (можно взять тут и подложить к себе https://github.com/megalloid/zynq_qmtech/blob/main/buildroot.config);

  40. Нажимаем F9 и запускаем процесс сборки: make -C /home/megalloid/buildroot O=» class=»formula inline»>PWD ARCH=arm BR2_JLEVEL=»(((nproc) — 1))»

  41. Ждём когда закончится компиляция, самое время заварить и выпить кофе =)

Теперь когда образ RootFS собрался надо его подписать и привести к виду, в котором его сможет корректно переварить U-Boot:

  1. Переходим в папку с готовыми образами: cd buildroot/images

  2. Производим подпись: mkimage -A arm -T ramdisk -C gzip -d rootfs.cpio uramdisk.image.gz

  3. Складываем uramdisk.image.gz на загрузочную SD-карту;

Настраиваем загрузку образов в U-Boot:

  1. Надо загрузиться в консоль U-Boot и записать туда: setenv mmc_boot “fatload mmc 0 0x2000000 uramdisk.image.gz; fatload mmc 0 0x4000000 uImage; fatload mmc 0 0x4500000 system.dtb; bootm 0x4000000 0x2000000 0x4500000;”

  2. Сохраняем эту переменную через saveenv и перезагружаемся;

  3. Дожидаемся загрузки ОС;

Теперь можно переходить к компиляции драйвера и непосредственно настройке Wi-Fi модуля.

Компиляция модуля ядра 88х2cs для Wi-Fi модуля

Переходим к последнему элементу, необходимому для работы Wi-Fi модуля — драйверу нашего Wi-Fi модуля. На просторах GITHUB я нашел вполне себе собираемый и рабочий вариант драйвера. Чтобы собрать драйвер out-of-tree нужно сделать следующее:

  1. Склонировать исходные коды себе: git clone https://github.com/megalloid/rtl88x2cs

  2. Переключим ветку на версию драйвера совместимую с 5.15: git checkout tune_for_jethub_5_14

  3. Запускаем компиляцию: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=»/home/megalloid/linux-xlnx» (где KSRC — путь до папки с ядром LInux из предыдущего шага);

  4. На выходе мы получим в этой папке файл 88x2cs.ko, его нужно положить туда же на загрузочную SD-карту;

Проверяем, загрузится ли модуль ядра и появится ли wlan-интерфейс:

  1. Монтируем SD-карту в файловую систему: mount /dev/mmcblk0p1 /mnt

  2. Переходим в каталог SD-карты: cd /mnt/

  3. Выполняем insmod 88x2cs.ko для загрузки модуля ядра;

Запускаем программу ifconfig — и видим, что появился wlan0 интерфейс:

Это говорит о том, что модуль ядра успешно загрузился и готов к работе. В следующей главе перейдем к настройке wlan0 интерфейса и проверке работоспособности Wi-Fi, прогону скорости через iPerf;

Про модули ядра можно почитать тут: https://habr.com/ru/post/117654/

Настройка Wi-Fi интерфейса и измерение скорости с использованием iPerf

Для настройки Wi-Fi необходимо первым делом создать конфиг, удобнее всего в этом случае его создать на SD-карте в виде файла wpa_supplicant.conf

Выполним в консоли Zynq следующие команды:

  1. Создадим файл конфигурации: touch /mnt/wpa_supplicant.conf

  2. Открываем его на редактирование: vi /mnt/wpa_supplicant.conf

  3. Записываем в него следующее содержимое и сохраняем: 

network={ ssid="ASUS" scan_ssid=1 key_mgmt=WPA-PSK psk="megapassword" }
  1. Запускаем wpa_supplicant: wpa_supplicant -B -Dnl80211 -iwlan0 -c/mnt/wpa_supplicant.conf -P/var/run/wpa_supplicant.pid -dd -f/mnt/wpa_supplicant.log

  2. Через некоторое время появится IP-адрес на интерфейсе wlan0 и будет установлена связь с роутером;

Теперь можно прогнать трафик с компьютера (который подключен к роутеру кабелем) на Zynq и обратно. Для этого нужно:

  1. Запустить на компьютере: iperf3 -s

  2. Запустить на Zynq: iperf3 -c <IP компьютера> -N -i1 -t60 -u 

  3. Чтобы пустить трафик в обратном направлении на Zynq нужно выполнить: iperf3 -c <IP компьютера> -N -i1 -t60 -R -u

Наблюдаем результат:

# iperf3 -c 192.168.2.3 -N -i1 -u -b300M Connecting to host 192.168.2.3, port 5201 [  5] local 192.168.2.233 port 55279 connected to 192.168.2.3 port 5201 [ ID] Interval           Transfer     Bitrate         Total Datagrams [  5]   0.00-1.00   sec  15.9 MBytes   133 Mbits/sec  11481   [  5]   1.00-2.00   sec  16.8 MBytes   141 Mbits/sec  12154   [  5]   2.00-3.00   sec  17.0 MBytes   143 Mbits/sec  12336   [  5]   3.00-4.00   sec  17.0 MBytes   142 Mbits/sec  12275   [  5]   4.00-5.00   sec  16.3 MBytes   137 Mbits/sec  11812   [  5]   5.00-6.00   sec  16.3 MBytes   137 Mbits/sec  11831   [  5]   6.00-7.00   sec  17.0 MBytes   143 Mbits/sec  12332   [  5]   7.00-8.00   sec  10.4 MBytes  87.4 Mbits/sec  7545   [  5]   8.00-9.00   sec  14.8 MBytes   124 Mbits/sec  10736   [  5]   9.00-10.00  sec  15.3 MBytes   128 Mbits/sec  11050    - - - - - - - - - - - - - - - - - - - - - - - - -  [ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams [  5]   0.00-10.00  sec   157 MBytes   132 Mbits/sec  0.000 ms  0/113552 (0%)  sender [  5]   0.00-10.03  sec   157 MBytes   131 Mbits/sec  0.066 ms  0/113551 (0%)  receiver iperf Done.  # iperf3 -c 192.168.2.3 -N -i1 -u -b300M -R Connecting to host 192.168.2.3, port 5201 Reverse mode, remote host 192.168.2.3 is sending [  5] local 192.168.2.233 port 50266 connected to 192.168.2.3 port 5201 [ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams [  5]   0.00-1.00   sec  12.0 MBytes   101 Mbits/sec  0.043 ms  0/8693 (0%)   [  5]   1.00-2.00   sec  12.2 MBytes   102 Mbits/sec  0.072 ms  0/8842 (0%)   [  5]   2.00-3.00   sec  12.6 MBytes   105 Mbits/sec  0.161 ms  16279/25380 (64%)   [  5]   3.00-4.00   sec  12.1 MBytes   101 Mbits/sec  0.032 ms  16435/25185 (65%)   [  5]   4.00-5.00   sec  12.5 MBytes   105 Mbits/sec  0.076 ms  17924/27002 (66%)   [  5]   5.00-6.00   sec  12.4 MBytes   104 Mbits/sec  0.320 ms  16380/25350 (65%)   [  5]   6.00-7.00   sec  12.6 MBytes   105 Mbits/sec  0.055 ms  16807/25910 (65%)   [  5]   7.00-8.00   sec  12.5 MBytes   105 Mbits/sec  0.196 ms  17230/26287 (66%)   [  5]   8.00-9.00   sec  12.3 MBytes   103 Mbits/sec  0.050 ms  16305/25184 (65%)   [  5]   9.00-10.00  sec  12.6 MBytes   106 Mbits/sec  0.100 ms  11046/20165 (55%)    - - - - - - - - - - - - - - - - - - - - - - - - -  [ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams [  5]   0.00-10.03  sec   359 MBytes   300 Mbits/sec  0.000 ms  0/259695 (0%)  sender [  5]   0.00-10.00  sec   124 MBytes   104 Mbits/sec  0.100 ms  128406/217998 (59%)  receiver  iperf Done.

Скорость в целом соответствует заявленным Xilinx цифрам: около 20 Мбайт\сек (около 150 Мбит\с). В итоге если будем иметь подходящие условия в эфире — эта скорость будет максимальной и в 2.4 ГГц и 5 ГГц. Результат соответствует всем ожиданиям.

Подведем итог

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


ссылка на оригинал статьи https://habr.com/ru/post/658487/

Теневые стеки для пользовательского пространства

Стек вызовов (call stack) является излюбленной целью злоумышленников, пытающихся скомпрометировать запущенный процесс; если злоумышленник найдет способ перезаписать адрес возврата в стеке, то он сможет перенаправить управление на код по своему выбору, что приведет к ситуации, которую лучше всего можно описать фразой «game over». Именно поэтому для защиты стека прикладывается очень много усилий. Одним из самых многообещающих методов является “теневой стек” (shadow stack); как следствие, множество различных процессоров должным образом поддерживают теневые стеки. С поддержкой защиты программ пользовательского пространства (user-space) с помощью теневых стеков дела обстоят не так хорошо; в настоящий момент она является предметом обсуждения в сообществе разработчиков ядра, но добавить эту функцию сложнее, чем может показаться на первый взгляд. Среди прочего, подобные патчи существуют уже достаточно долго, чтобы у них самих появились собственные проблемы с обратной совместимостью.

Основы теневых стеков

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

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

Процессоры Intel, помимо всего остального, обеспечивают такую ​​поддержку. Если теневой стек был подключен (что является привилегированной операцией), помещение адресов возврата в этот стек и сравнение при возврате функции выполняются самим процессором. Такой теневой стек обычно не доступен для записи из приложения (кроме как с помощью инструкций вызова функции и возврата) и, следовательно, не может быть скомпрометирован злоумышленником. Аппаратная поддержка также предполагает наличие специального «токена восстановления» (restore token) для теневого стека, который, среди прочего, гарантирует, что два процесса не смогут использовать один и тот же теневой стек — ситуация, которая, опять же, облегчила бы атаку.

Поддержка теневых стеков пользовательского пространства

Актуальная версия патчей для поддержки теневых стеков была опубликована Риком Эджкомбом (Rick Edgecombe); большая часть самих патчей была написана Ю-ченг Ю (Yu-cheng Yu), которым уже было опубликовано множество более ранних версий этой работы. Для подключения этой функции требуется 35 нетривиальных патчей, но и они еще не решают проблему полностью. Кто-то может задаться вопросом, почему это так сложно, ведь теневые стеки производят впечатление функционала, который почти всегда может быть просто проигнорирован большей частью кода. Но жизнь никогда не бывает такой простой.

Как и следовало ожидать, ядро ​​должно содержать код для управления теневыми стеками пользовательского пространства. Сюда входит включение этой функции на уровне процессора и передача ее для каждого конкретного процесса. Каждому процессу требуется собственный теневой стек с соответствующим токеном восстановления, а затем на него должен быть нацелен (привилегированный) регистр с указателем на теневой стек. Также нужно обрабатывать ошибки: как обычные отказы страниц (page faults), так и такие вещи, как обнаружение нарушения целостности (integrity-violation traps). И еще больше информации, которой нужно управлять при переключении контекста. Это все довольно стандартно для новой функции такого рода.

Память, выделенная под сам теневой стек, должна обрабатываться особым образом. Она принадлежит пользовательскому пространству, но код пользовательского пространства обычно не должен иметь права производить в нее запись. Также процессору нужно как-то распознавать память, выделенную для теневых стеков, поэтому они должны быть помечены специальным образом в таблице страниц, и здесь все становится немного интереснее. В каждой записи в таблице страниц (page-table entry — PTE) отведено несколько битов для описания применяемых средств защиты и других различных типов состояния, но архитектура x86 не включает бит “это страница теневого стека”. Но все-таки есть некоторые биты PTE, зарезервированные для использования операционной системой; Linux не использует их все и мог бы выделить один для этой цели, но очевидно, что некоторые другие операционные системы не располагают свободными битами PTE, поэтому присвоение одного из них в наших целях не приветствуется.

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

К сожалению, разработчики ядра Linux уже успели прийти к такому же выводу много лет назад, поэтому у Linux есть своя интерпретация этой комбинации битов PTE. Именно так ядро ​​помечает страницы для копирования при записи (copy-on-write). Отсутствие доступа для записи вызовет ловушку, если процесс попытается записать страницу; наличие грязного бита укажет ядру сделать копию страницы и предоставить процессу к ней доступ для записи. И все работает как надо, если только процессор не применяет собственную интерпретацию этой комбинации битов. Так что большая часть патчей, о которых мы говорили выше, сосредоточена на захвате одного из этих неиспользуемых битов PTE для нового флага _PAGE_COW и обеспечении его использования управляющим памятью кодом.

Сложности, которые приносят теневые стеки на этом не заканчиваются. Если процесс вызывает clone(), для дочернего процесса должен быть выделен новый теневой стек; ядро выполняет эту задачу автоматически. Сигналы, как всегда, добавляют свои сложности, так как они уже включают в себя различные манипуляции со стеком. Дела обстоят еще хуже, если процесс установил альтернативный стек для обработчиков сигналов с помощью sigaltstack() — до такой степени, что текущий набор патчей вообще не обрабатывает этот случай. Именно из таких деталей (и не только) и формируется длинная серия патчей.

Проблемы с ABI

Использование теневых стеков должно быть полностью прозрачным для большинства приложений; в конце концов, разработчики редко продумывают все нюансы касательно стека вызовов в своей работе. Но всегда будут приложения, которые делают со своими стеками достаточно сложные вещи, начиная с многопоточных программ, которые явно управляют областью стека для каждого потока. Другие программы могут помещать в стек свои собственные специально созданные “переходники” (thunks). Без особой осторожности все эти приложения будут ломаться, если они внезапно будут настроены для работы с теневым стеком. Такого рода массовая регрессия, как правило, делает средства защиты непопулярными, поэтому были приняты различные меры, чтобы избежать этого.

Тщательно продуманный план, к которому в результате пришли разработчики, заключался в том, чтобы пометить приложения (специальным свойством в разделе ELF .note.gnu.property), которые готовы к работе с теневыми стеками. Приложения, которые не предполагают никаких замысловатых манипуляций со стеком, могут быть просто перестроены и впоследствии запущены с теневыми стеками. Для более сложных случаев был определен набор операций arch_prctl(), позволяющий явно манипулировать теневыми стеками. Библиотека GNU C была расширена, чтобы использовать эти вызовы для правильной настройки среды при запуске приложения, а ядро ​​включало теневые стеки всякий раз, когда запускалась соответствующим образом помеченная программа. Некоторые дистрибутивы, включая Fedora и Ubuntu, создают свои двоичные файлы для теневых стеков; все, что им нужно, — это правильно оборудованное для работы с дополнительной степенью защиты ядро​​.

Всегда опасно выпускать код, использующий функции ядра, которые еще не приняты и не замержены; теневые стеки оказываются ярким примером почему. Согласно сопроводительному письму к текущей серии, API arch_prctl() был “заброшен по причине своей странности”. Но исполняемые файлы, готовые к работе с теневыми стеками, которые уже были развернуты в системах по всему миру, создавались с расчетом на присутствие этого API, странного или нет; если ядро ​​учитывает маркировку в файле ELF и подключает теневые стеки для этих программ, некоторые из них не будут работать. Это заставит системных администраторов по всему миру отключить теневые стеки по крайней мере до 2040 года, что скорее сведет на нет все усилия в рамках этого замысла.

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

Предлагаемый интерфейс

Полный контроль над функциональностью теневого стека должен осуществляться с помощью вызова arch_prctl() (как это ни странно):

status = arch_prctl(ARCH_X86_FEATURE_ENABLE, ARCH_X86_FEATURE_SHSTK);

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

Хотя большинству приложений не нужно беспокоиться о теневых стеках, некоторые из них должны иметь возможность создавать таковые. Приложения, использующие makecontext() и подобные ей функции, являются ярким примером. Для создания теневого стека требуется поддержка ядра; связанная память должна включать токен восстановления, а специальные биты страницы должны быть установлены, как было описано выше. Для этой операции есть новый системный вызов:

void *map_shadow_stack(unsigned long size, unsigned int flags);

Желаемый размер стека передается как size. Для параметра flags есть только одно значение: SHADOW_STACK_SET_TOKEN, чтобы запросить сохранение токена восстановления в стеке. Возвращаемое значение в случае успеха является адресом основания этого стека.

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

Приложениям, выполняющим особо сложные задачи, может потребоваться возможность записи в теневой стек. Это обычно не разрешено по очевидным причинам, но, как отметил Эджкомб, это “чересчур ограничивает любые приложения, которые потенциально могут захотеть делать какие-либо экзотические вещи за счет небольшой безопасности”. В таком экзотическом случае можно включить другую функцию (LINUX_X86_FEATURE_WRSS) с помощью arch_prctl(); это, в свою очередь, включает инструкцию WRSS, которая может записывать в память теневого стека. Прямая запись в эту память путем разыменования указателя в этом случае по-прежнему запрещена.

Что дальше?

Эта работа не так уж нова; ее ранняя версия была рассмотрена в этой статье 2018 года. В предыдущем воплощении набор патчей для теневого стека дошел до 30-й версии. Другая половина работы по обеспечению целостности потока управления (непрямое отслеживание ветвлений), которая дошла до 29-й версии, на данный момент отложена (хотя Питер Зийлстра (Peter Zijlstra) как раз представил отдельную реализацию). Есть надежда, что с новым разработчиком, возглавляющим работу, сокращением объема и некоторыми запрошенными изменениями, эта работа, наконец, сможет продвинуться в основную ветку.

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

Также был отдельный сабтред, посвященный проблемам с  Checkpoint/restore in user space (CRIU), которая использует множество закулисных трюков, чтобы выполнить свою работу. Часть процесса создания контрольной точки включает в себя внедрение кода-“паразита” в процесс, для которого создается контрольная точка, получение необходимой информации, а затем выполнение специального возврата из паразита для возобновления нормального выполнения. Это как раз тот вид вмешательства в поток управления, для предотвращения которого предназначены теневые стеки. Обсуждались различные возможные решения, но на данный момент ничего не вылилось в конкретный код. Решение этой проблемы также кажется необходимым, прежде чем можно будет замержить теневые стеки; как сказал Томас Гляйкснер (Thomas Gleixner): “Мы не должны ломать системы CRIU обновлениями ядра”.

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


Приглашаем всех желающих на открытый урок «Введение в docker». На занятии мы рассмотрим основы контейнеризации и ее отличие от виртуализации, плавно перейдем к рассмотрению самого популярного на данный момент инструмента контейнеризации Docker — узнаем из каких основных компонентов и сущностей он состоит, и как они взаимодействуют между собой. Регистрация по ссылке.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/658491/

Парадокс Ферми – вовсе не парадокс, а вопрос; в чём он состоит, и как его решать (часть 4)

Предыдущая часть

Водные и океанические миры


Кадр из фильма «Водный мир», ставшего переломным в карьере Кевина Костнера

Ещё две гипотезы, объясняющие отсутствие в нашей Галактике наблюдаемой активности разумных инопланетян, хотя и технически являются отдельными, но, по сути, приводят нас к одному и тому же выводу. Кроме того, обе они имеют дело с одним и тем же веществом – универсальным растворителем и ключевым ингредиентом жизни (насколько нам известно): водой.

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

Ввиду очень бедной выборки планет, на которых найдена даже не разумная жизнь, а просто жизнь – нам известна всего одна такая планета – учёные вынуждены делать выводы на основе условий, существующих на Земле. Хотя поверхность нашей планеты покрыта океанами на 71%, по массе вся эта вода составляет всего лишь 0,02% от массы остального вещества. Если бы воды было хоть немного больше, вся планета оказалась бы покрытой многокилометровой толщей воды, что радикально уменьшило бы вероятность появления на ней разумной жизни.

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

Многие же морские обитатели за это время не изменились, поскольку в море всегда полно еды, а условия меняются несущественно – идеально приспособившись к жизни в какой-либо нише, эволюционировать не нужно. Те же осьминоги, довольно умные по-своему животные, существуют в более-менее неизменном виде уже почти 100 миллионов лет, и развиваться не планируют. Дельфины, ещё одни умные морские существа, происходят от существ, вышедших на сушу. И тем хватило ума вернуться обратно в море, где они не знают забот уже почти 50 млн лет. Их большой мозг развился, судя по всему, благодаря развитой социальной структуре и необходимости строить картину окружающего мира посредством эхолокации. Но ни осьминоги, ни дельфины, насколько нам известно, не строят телескопы и не планируют выбираться не то что на орбиту планеты – даже на сушу.

Задолго до современного праздника по массовому открытию экзопланет (а недавно их количество перевалило за 5000), в 1983 году, Глен Дэвид Брин в работе «Великое молчание: дебаты по поводу внеземного разума» утверждал, что на планетах с небольшим количеством суши или полным её отсутствием появление разумных существ, использующих орудия труда, представляется маловероятным.


«Планета-глаз» в представлении художника: водный мир, на котором на стороне, обращённой к солнцу, поверхность воды поддерживается в жидком состоянии, тогда как в остальных местах она покрыта льдом

Ближе к современности, в работе 2018 года М. Лингэм и А. Леб «Зависимость биологической активности от процента покрытия планеты водой» подсчитали, что процессы, ведущие к появлению эволюции живых существ, должны происходить только на планетах с определённым процентом покрытия поверхности водой – от 30% до 90%. Кроме того, по их расчётам выходит, что планет, у которых 70% поверхности покрыто водой – как у нашей Земли – во Вселенной чрезвычайно мало.

Также в 2018 году международная группа учёных, проанализировав данные, полученные телескопами «Кеплер» и «Гайя», построили модель, описывающую состав экзопланет. У них вышло, что планеты в 2,5 раза больше (и в 10 раз массивнее) Земли, вероятно, на 50% состоят из воды – то есть, являются водными мирами. В итоге у учёных получилось, что 35% экзопланет тяжелее Земли должны быть очень богаты водой.

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

Вероятно, миров с жидкой водой на поверхности в нашей Галактике хватает, но именно её изобилие, по иронии судьбы, и делает разумную жизнь столь редким явлением.

Другая вариация гипотезы о влиянии воды на развитие разумной жизни касается т.н. «океанических» миров. В отличие от «водных» планет, покрытых жидким океаном, «океаническими» мирами называются планеты, поверхность которых скована коркой льда. Такие планеты, скорее всего, находятся за снеговой линией – достаточно далеко от своей звезды для того, чтобы простые летучие соединения (такие как вода, аммиак, метан, молекулярные азот и хлор) переходили в твёрдое состояние.

При этом под поверхностью льда вполне может существовать жидкий океан – например, за счёт тепла ядра планеты или радиоактивного распада в породе. Или же, как в известном, почти классическом примере Европы, спутника Юпитера – за счёт приливных сил, активно «разминающих» спутник по мере его вращения вокруг газового гиганта. Средняя температура на экваторе Европы составляет около -160 °C, а на полюсах -220 °C, поэтому лёд на поверхности должен быть чрезвычайно прочным и толстым.


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

В 2013 году учёные из Калифорнийского технологического института предположили, что подлёдный океан Европы не изолирован от окружающей среды и обменивается газами и минералами с залежами льда на поверхности. А над южной полярной областью спутника через толщу льда пробиваются гейзеры – пар вылетает из них со скоростью 700 м/с на высоту до 200 км. Их в 2012 году заметили при помощи «Хаббла». Подобные же гейзеры зафиксированы и на Энцеладе, спутнике Сатурна.

Доктор Лунна Куик, планетолог из НАСА, считает, что наличие подлёдного океана на планете, в котором накапливается много энергии, говорит о том, что там вполне может зародиться и жизнь.

Принято считать, что жизнь существует в «зонах обитаемости» — на таком расстоянии от звезды, на котором на поверхности планеты может существовать жидкая вода. Если на каком-нибудь небесном теле в Солнечной системе удастся найти жизнь, развившуюся в условиях подлёдного океана, это станет величайшим научным открытием. Однако даже если в таких условиях и может возникнуть разумная жизнь (а вероятность этого ещё меньше, чем у появления разумной жизни в «водном мире»), сложно представить, что подобные существа будут пробиваться на поверхность планеты через лёд, чтобы затем отправляться в космос или хотя бы посылать наружу радиосигналы.

Гипотеза Авроры

В 2015 году писатель Ким Стенли Робинсон выпустил роман «Аврора». Он рассказывает историю корабля поколений запущенного в 2545 году с Сатурна к звезде Тау Кита для создания человеческой колонии и о возвращении к Земле после провала миссии. Повествование в романе ведется от лица компьютерного искусственного интеллекта звездного корабля.

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

На основе романа исследователи в 2019 году написали работу «Парадокс Ферми и эффект Авроры: колонизация, расселение и устойчивые состояния внеземных цивилизаций». В работе изучалась скорость расселения возможной цивилизации по Галактике с учётом того, что не все потенциально обитаемые планеты могут оказаться пригодными для заселения. Кроме того, некоторые миры могут оказаться уже занятыми жизнью, и из этических или иных соображений цивилизация может обойти их стороной.

В результате получилось, что время расселения цивилизации по всему Млечному пути сравнимо с возрастом галактики (порядка 13,5 млрд лет). Кроме того, могут существовать ограничения на дальность расселения цивилизации (из-за проблем с коммуникациями на больших расстояниях, например), или же время жизни цивилизации может быть сильно ограниченным. Есть вероятность, что Землю посещали (или до неё доходили характерные сигналы) представители других цивилизаций, однако никаких следов этого не осталось – современные люди существуют около 200 000 лет, а какие-то обрывочные записи ведутся лишь последние 6000 лет.

Парадокс SETI


Послание Аресибо и телескоп Аресибо.

Проект SETI – это комплекс экспериментов и мероприятий, направленных на поиски сигналов, испущенных внеземными цивилизациями. Началом проекта считается опубликованная в 1959 году в международном научном журнале Nature статья Дж. Коккони и Ф. Мориссона «Поиски межзвёздных сообщений». В этой статье было показано, что даже при тогдашнем уровне развития радиоастрономии можно было рассчитывать на обнаружение внеземных цивилизаций примерно такого же технологического уровня, как земной, при условии, что они обитают на не слишком далёких от нас планетах, в планетных системах звёзд солнечного типа.

При этом, наверное, первым примером экспериментов такого рода можно считать работу, которую провёл не кто иной, как знаменитый физик и экспериментатор Никола Тесла. В 1899 году он трудился в своей лаборатории в Колорадо-Спрингс, и ему показалось, что он поймал внеземной радиосигнал, прекратившийся после захода Марса. Однако дальнейший анализ не подтвердил достоверность этой гипотезы. Сигнал можно было объяснить помехами с соседней экспериментальной станции, а также проходом одного из спутников Юпитера через магнитное поле планеты.

В рамках проекта SETI учёные сначала пытались искать сигналы на частоте радиолинии излучения нейтрального атомарного водорода во Вселенной (длина волны 21 см). В 1960 г. Фрэнк Дрейк запустил проект «Озма» (названный в честь сказочной принцессы страны Оз); сигналы предполагалось искать при помощи 25-метрового радиотелескопа в полосе шириной 400 КГц около частоты в 1420 ГГц. В качестве объектов для поисков сигналов были выбраны две близлежащие звезды солнечного типа — Тау Кита и Эпсилон Эридана.

Не добившись успеха, учёные продолжали развивать и совершенствовать оборудование и эксперименты, а также умножать количество участвующих в поисках телескопов и даже привлечь обычных людей к обсчётам результатов при помощи распределённого проекта SETI@Home. За всё время удалось обнаружить только один сигнал, происхождение которого, вероятно, может быть искусственным – по крайней мере, его характеристики хорошо совпали с ожидаемыми от такого сигнала. Это сигнал «Wow!», полученный в 1977 году телескопом «Большое ухо».

Проект SETI по определению является «пассивным» — то есть, занимается прослушиванием космоса в поисках сигналов, испущенных иными цивилизациями. В пику ему в 2006 году был создан проект METI – попытка налаживания контакта с инопланетянами путём отправки специально сконструированных сигналов с Земли.

Это тоже был не первый пример намеренной отправки сигнала в космос. Первыми, вероятно, стали советские учёные из крымского центра дальней космической связи в Евпатории. Они отправили зашифрованное азбукой Морзе послание «Мир», «Ленин», «СССР» в сторону планеты Венера, а затем уловили отражённый сигнал при помощи планетарного радара, проверив таким способом его работоспособность.

Самым известным и мощным сигналом, отправленным с Земли специально для инопланетян, было послание Аресибо. Радиосигнал был послан 16 ноября 1974 года в честь открытия телескопа в обсерватории Аресибо в направлении шарового звёздного скопления М13, находящегося на расстоянии 25 000 световых лет в созвездии Геркулеса. В сообщении были зашифрованы стилизованные изображения телескопа, человека и молекул, лежащих в основе жизни.

Однако различные сообщения, предназначенные для внеземных разумов, отправлялись и отправляются регулярно. С 1999 по 2003 год из того же Евпаторийского центра дальней космической связи было отправлено три сообщения в сторону различных ближайших звёзд. В них были закодированы алфавит, цифры, таблица Менделеева и различные научные концепции.

В 2008 году в честь 40 лет с момента записи песни Beatles «Across the Universe», 45 лет с момента основания сети дальней космической связи НАСА и 50 лет со дня основания НАСА с одной из «тарелок» сети эту песню отправили в направлении Полярной звезды.

В 2012 году в честь 35 лет с момента получения сигнала «Wow!», из обсерватории Аресибо ушла в космос радиопередача, содержащая более 10 000 сообщений из Твиттера и видеороликов.

В 2016 году сообщение «Простой ответ на элементарное сообщение» было отправлено со станции Себрерос, одной из станций дальней космической связи Европейского космического агентства. В сообщении содержались ответы различных людей на вопрос о том, как наше текущее взаимодействие с окружающей средой повлияет на наше будущее, а также фотографии различных мест планеты.

Однако ответов на эти «звонки» мы пока не получали. Более того, многие учёные критикуют подход «активного поиска» внеземной жизни. Среди них — такой именитый физик, как Стивен Хокинг, а также известный учёный и автор Дэвид Брин. Последний в статье «Нужно ли нам кричать в космос» писал, что Карл Саган и Филип Моррисон настоятельно рекомендовали «детишкам», столкнувшимся со странным и неопределённым космосом, вести себя тихо, слушать своё окружение, терпеливо изучать Вселенную и сравнивать свои наблюдения, перед тем, как начинать шуметь в джунглях, которые они до конца не поняли.

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

Заключение

Таковы самые популярные на сегодняшний день гипотезы, пытающиеся ответить на вопрос Ферми: «А где все?» Все они в той или иной степени страдают от одной и той же проблемы: все предположения мы вынуждены делать на основе данных, собранных только об одной известной там технологически развитой цивилизации – нас самих.

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


ссылка на оригинал статьи https://habr.com/ru/post/658503/

Безопасность в компании: хоть в лоб, хоть по лбу

Когда-нибудь ты станешь немощен и слаб —
Делай бэкап, давай делай бэкап,
На случай, если укусит радиоактивный краб —
Делай бэкап, давай делай бэкап!
НТР

Только не так!

31 марта весь айтишный мир готовится к 1 апреля. Нет, в компаниях не разрисовывают красным кетчупом пол, не обводят мелом контуры тел, не перетыкают мышки и даже уже не заклеивают лазеры. Всё в прошлом — ну почти. Все готовятся к ненормальному дню атак и таким образом отмечают день бэкапа. И можно каждый год (месяц, день) писать про резервное копирование на Хабре, развешивать плакаты о необходимости бэкапов и инфобеза, но под клавиатурой коллеги всегда найдётся листочек с хитрым паролем qwerty123, в браузере — куча незащищённых платёжных средств, а пароль на вход в CRM и вовсе будет пустым (логин, конечно, admin). Всё потому что компаниям, особенно в секторе малого и среднего бизнеса, затраты на безопасность кажутся роскошью, а не средством выживания. А зря. Как показали панельные беседы на ЦИПР-2021, для современных злоумышленников практически нет преград, они в своём развитии могут основательно опережать средства защиты информации и IT-инфраструктуры: им даже воздушный зазор не помеха. Так вы хоть бэкап-то сделайте, а?

Даём справку. День бэкапа — одна из популярных дат у айтишников по всему миру. Он был учреждён по решению пользователей Reddit и призван привлечь внимание бизнеса, IT-сектора и простых пользователей к вопросам защиты и гарантированного сохранения информации, рассказать о рисках потери данных в частном и коммерческом сегменте. Лучший способ отметить день бэкапа — создать резервные копии данных на ПК, проверить в соответствии с правилами, протестировать и надёжно сохранить. И только потом пицца и остальные радости жизни 🙂

А чё, а чё, всё плохо?

Больше чем. До 2020 года проблемы в сфере информационной безопасности вызывали огромную, беспрецедентную тревогу специалистов по защите данных, но 2020, 2021 и 2022 степень опасности увеличилась в разы: риски атак, взломов, хищения данных и компрометации IT-инфраструктуры любой компании и любого пользователя возросли вместе с распространением удалённой работы, коронавирусных ограничений и социально-политической напряжённости на планете в целом. Информация и данные — ценные, дорогостоящие и порой невоспроизводимые ресурсы, воздействие на которые оказывает не меньшее (а то и большее) влияние, чем манипуляции с жизнеобеспечением, питанием и транспортом.

Не будем голословны — давайте обратимся к отчёту Positive Technologies за 2020 год. Согласно ему, ​​количество уникальных киберинцидентов в 2020 году выросло на 51% по сравнению с 2019 годом. Семь из десяти атак носили целенаправленный характер. Наиболее интересные отрасли, по мнению злоумышленников, — это государственные и медицинские учреждения, промышленные предприятия. Атак с использованием вредоносного ПО с каждым годом становится всё больше и больше. В 2020 году количество таких атак увеличилось на 54% относительно прошлогоднего показателя.

Если посмотреть на векторы атак, то объектами становятся компьютеры, серверы и сетевое оборудование — во всех сферах. Важным объектом атаки остаются люди, подверженные методам социальной инженерии, которые становятся всё изощрённее. Для сферы торговли и прочих компаний, среди которых немало субъектов малого и среднего бизнеса, также характерны атаки на веб-ресурсы. По-прежнему основными мотивами злоумышленников выступают финансовая выгода и получение данных, которые можно использовать, продавать или же запускать как базовый элемент нанесения репутационного урона.

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

  • не имеют службы безопасности и даже человека, ответственного за вопросы информационной безопасности в компании;
  • отказываются от идеи найма системного администратора и пользуются услугами недорогих компаний-аутсорсеров или самозанятых одиночек (увы, при всём уважении, нередко это ребята уровня эникея);
  • не замыкают контур информационной и экономической безопасности, а локально внедряют отдельные решения и латают дыры, обеспечивая лоскутную (никакую) защиту;
  • не обучают сотрудников и недооценивают риски со стороны персонала;
  • полагают, что вне контура компании удалённый сотрудник — самостоятельный, ответственный и организованный человек, заботящийся о кибербезопасности и защите данных;
  • считают, что их «вшивая клиентская база» и «тупое ноу-хау» нафиг никому не нужны.

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

Где риски зарыты?

▍ В людях

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

  1. Сотрудники, которым пофиг на безопасность. Они не задумываются о сложности паролей, о политике использования личных устройств в офисе, их не волнует принесённый на работу пиратский софт, им нормально передавать информацию и документы в чатах и через личную почту. Как правило, это никакие не злоумышленники — обычные сотрудники, которых не потрудились обучить основам работы с информацией и коммерческой тайной. Эта часть команды (а то и вся команда) — серьёзная брешь в информационной и экономической безопасности. Ну а если уж они считают, что с компьютером на ты и могут наконфигурячить что-то в настройках корпоративных систем, то только бэкапы вас и спасут — актуальные, проверенные, по 2 копии на трёх разных серверах (в иных хранилищах).
  2. Удалённые сотрудники — в принципе, те же безалаберные ребята из первого пункта, только с поправкой на работу в незащищённых сетях и средах. Могут ненароком нанести удар по компании и утратить важные данные по неосторожности.
  3. Третья группа — опасный, умный и осторожный враг. Это сотрудники, которые могут целенаправленно «сливать» данные, передавать их конкурентам, продавать, компрометировать со злым умыслом. У них, как правило, есть план и помощники, поэтому они могут долгое время действовать незаметно.

Человек уязвим перед угрозами безопасности и чаще всего выступает основным объектом атаки, поскольку «пробить» можно даже самого подкованного пользователя. Социальная инженерия, сложные схемы взаимодействия и психологические приёмы открывают злоумышленникам самый широкий доступ к корпоративной и частной информации. Поэтому сотрудники в компании должны быть осведомлённым, обученным и всегда актуально информированным звеном.

▍ В жадности

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

  • Если свой сервер, то утеря данных может произойти вместе с выходом из строя жёстких дисков и иного оборудования. При этом как показывает опыт, никого не смущают ни высокие температуры дисков, ни зависания, ни скрежет в системе. Работает и ладно, правда же?
  • Если данные в облаке, компании не озабочены приобретением собственного надёжного VDS, а используют облачные порталы компаний-вендоров программного обеспечения. Потом начинаются сюрпризы: облачные сервисы взламывают, компании-разработчики меняют тарифы или, например, не отдают бэкапы и данные ввиду странового риска.
  • Если купленный VDS, компания не заботится об администрировании хранилищ и сервера, а полагается на поставщика облачных технологий. Хоть это и довольно надёжная стратегия, лучше всё-таки не пускать контроль данных и мониторинг на самотёк.

Данные стоят дорого, даже если руководителю компании кажется, что они «завелись» бесплатно. И именно поэтому данные и коммерческая информация становятся объектом охоты — кто-то знает их реальную цену и ценность лучше. Поэтому стоит не ждать ситуации «жадность фраера сгубила», а позаботиться о своевременном обслуживании всей IT-инфраструктуры и подменного фонда, который должен быть строго обязательно даже в небольших компаниях. Такой подход при видимых инвестициях здорово оптимизирует затраты, бережёт нервы и обеспечивает бесперебойную работу компании.

▍ В форс-мажорах

Компания не может находиться в изоляции, поэтому она подвержена всей совокупности рисков: страновым, экономическим, природным. Как правило, это те риски, которые не поддаются прогнозированию, наступают внезапно и резко бьют по самым неподготовленным и незащищённым компаниям. Проблемы возникают как раз у тех, кто изначально допускал «дыры» в безопасности и не контролировал процессы управления информацией.

Согласно отчёту о потере данных в Европе, более 6% ПК ежегодно страдают от потери данных — так, в 2020 году произошло более 1,7 миллиона инцидентов. В отчёте определены шесть основных причин потери данных:

  • Отказ оборудования, в том числе повреждение из-за скачка напряжения и отказа диска — 42%
  • Человеческая ошибка, включая случайное удаление информации — 31%
  • Повреждение файлов программного обеспечения, случайное и преднамеренное — 13%
  • Вирусы — 7%
  • Кража, особенно кража ноутбуков — 5%
  • Потеря оборудования, включая наводнения, пожары, молнии, сбои в подаче электроэнергии и прочие форс-мажоры — 3%

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

Что здесь происходит?

Что делать?

Если вы думаете, что обойдётся или пронесёт, то нет. Управлять рисками придётся — этим может заниматься сисадмин, хороший аутсорсер или руководитель компании при должных компетенциях.

  • Обучение сотрудников с постоянным обновлением информации. Можно создать специальный раздел в интранете или просто проводить обучение, главное — обязательно тестировать сотрудников и проверять, что знания усвоены и актуализированы. Чтобы не столкнуться с сопротивлением, можно использовать приёмы геймификации или любую другую мотивацию.
  • Контроль доступа сотрудников. Все сотрудники, работающие с данными, коммерческой информацией и корпоративными информационными системами, должны чётко понимать, что такое конфиденциальность и какие последствия наступают в случае её несоблюдения. Компания должна не раздавать максимальные права всем подряд как знак великого доверия, а управлять доступами к разным типам данных, уметь защищать информацию в своей зоне ответственности от потери, повреждения и утраты.

Хотим отдельно обратить внимание, что всяческие bossware-системы (следилки) не имеют ничего общего с лучшими практиками обеспечения информационной безопасности. Это не что иное, как отличный способ демотивации и наращивания рисков наступления неблагоприятных событий, связанных с человеческим фактором.

  • Анализ угроз. В компании должен быть человек, который способен обеспечить своевременный и качественный мониторинг, чтобы быстро сориентироваться и провести профилактику потенциальных угроз, в том числе связанных с фишингом и социальной инженерией.
  • Разработка контура информационной безопасности — важная и часто дорогостоящая задача. Но лучше завершить её один раз и актуализировать отдельные компоненты, чем расплачиваться за каждый провал.
  • Мониторинг оборудования и IT-инфраструктуры — обязательное условие благополучия компании. Если вы IT-компания, то какого фига вы ещё читаете эту статью, всё давно должно работать 🙂 Если компания не относится к IT-сфере, необходимо нанять сотрудника, обратиться за помощью к надёжному крупному аутсорсеру, системному интегратору или даже вендору ваших корпоративных систем. Как правило, можно найти разумный вариант за разумные же деньги.
  • Работа с надёжными, проверенными поставщиками IT-услуг, программного обеспечения и элементов IT-инфраструктуры. Не поленитесь провести минимальную проверку репутации и надёжности поставщиков, проведите несколько раундов презентаций и обсуждения перед покупкой, задайте самые неудобные вопросы. Порядочную компанию это не смутит.
  • Развитие лояльности сотрудников и корпоративной культуры. Если компания способна выстроить адекватную корпоративную культуру, основанную на доверии, поддержке и принятии ошибок, рано или поздно сотрудники примут правила игры и будут более лояльными, а значит, менее опасными. Ещё раз обращаем внимание, что любой контроль сотрудников должен быть мягким, адекватным, точечным, а главное, сотрудники должны быть информированы о способах контроля и согласны с ними. Прозрачность отношений порождает ответную прозрачность, риски снижаются.
  • Бэкапы. Бэкапы. Ещё раз бэкапы. (Аааааа, эээх, всё равно не сделаете).

Иногда, когда смотришь на организацию информационной и экономической безопасности, хочется посоветовать хотя бы приложить подорожник. По крайней мере, это будет признаком того, что в компании хотя бы догадались взглянуть на проблему. Друзья, мы живём в непростое время, когда наши данные дорого стоят и они всегда кому-то нужны — даже если вы просто ставите окна в Костроме или пилите симпатичную аркаду под Android. Сохранность данных — это безопасность бизнеса, сотрудников, всего заработанного капитала. Поверьте, труды по обеспечению информационной безопасности стоят результата.

И да, прямо сейчас сделайте/протестируйте/проверьте свои бэкапы. Мы точно знаем, насколько это важно.


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/658307/

Проверка Barotrauma статическим анализатором PVS-Studio

Barotrauma – игра, в которой можно поуправлять подлодкой, попрятаться от монстров и даже поиграть на аккордеоне в попытке не пойти ко дну. Посмотрим, как проект, начатый инди-студией Undertow Games и продолженный совместно с FakeFish, выглядит изнутри. Для этого исследуем исходный код, преимущественно написанный на языке C#, с помощью статического анализатора PVS-Studio.

Введение

Barotrauma – многопользовательский космический 2D-симулятор подлодки в жанре survival horror. Игроку предстоит управлять подлодкой, отдавать приказы, устранять протечки и противостоять опасностям.

Barotrauma не является Open Source проектом в обычном понимании. Ранняя версия игры доступна бесплатно, текущая версия доступна в Steam. Разработчики опубликовали исходный код на GitHub для того, чтобы сообщество игроков могло свободно разрабатывать более комплексные моды и находить существующие ошибки.

Результаты проверки

Ошибки в if

V3001 There are identical sub-expressions ‘string.IsNullOrEmpty(EndPoint)’ to the left and to the right of the ‘||’ operator. BanList.cs 41

public bool CompareTo(string endpointCompare) {   if (string.IsNullOrEmpty(EndPoint) || string.IsNullOrEmpty(EndPoint))    { return false; }   .... } 

Значение EndPoint проверяется дважды. Разработчик, скорее всего, забыл поменять параметр EndPoint на endpointCompare при копировании метода string.IsNullOrEmpty. Вообще программисты очень часто ошибаются в методах сравнения. Рекомендую почитать статью коллеги об этом.

V3004 The ‘then’ statement is equivalent to the ‘else’ statement. ServerEntityEventManager.cs 314

public void Write(Client client, IWriteMessage msg,                    out List<NetEntityEvent> sentEvents) {   List<NetEntityEvent> eventsToSync = null;   if (client.NeedsMidRoundSync)   {     eventsToSync = GetEventsToSync(client);   }   else   {     eventsToSync = GetEventsToSync(client);   }   .... } 

Вне зависимости от значения client.NeedsMidRoundSync будет выполняться одно и то же. Возможно следует убрать else-ветвь или переработать её поведение.

Следующий фрагмент кода вызвал два предупреждения:

  • V3021 There are two ‘if’ statements with identical conditional expressions. The first ‘if’ statement contains method return. This means that the second ‘if’ statement is senseless DebugConsole.cs 2177

  • V3022 Expression ‘args.Length < 2’ is always false. DebugConsole.cs 2183

private static void InitProjectSpecific() {   ....   AssignOnClientRequestExecute(     "setclientcharacter",     (Client senderClient, Vector2 cursorWorldPos, string[] args) =>     {       if (args.Length < 2)       {         GameMain.Server.SendConsoleMessage("....", senderClient);         return;       }        if (args.Length < 2)       {         ThrowError("....");         return;       }     );   .... } 

Две одинаковые проверки. В случае выполнения условия первого if метод завершит работу, иначе же обе then-ветви не будут выполнены.

При такой работе сообщение будет отправляться, но запись ошибки методом ThrowError не произойдёт. Следует объединить два тела if или изменить условие второго.

V3022 Expression ‘newPrice > 0’ is always true. DebugConsole.cs 3310

private static void PrintItemCosts(....) {   if (newPrice < 1)   {     NewMessage(depth + materialPrefab.Name +      " cannot be adjusted to this price, because it would become less than 1.");     return;   }    ....    if (newPrice > 0)   {     newPrices.TryAdd(materialPrefab, newPrice);   }   .... } 

Если newPrice меньше или равен 0, следует выполнение тела первого if. После этого исполнение метода завершается. Следовательно условие второго if всегда будет истинно. Поэтому можно добавить тело второго *if *в else-ветвь первого или же вовсе его убрать.

Опечатки

V3005 The ‘arrowIcon.PressedColor’ variable is assigned to itself. ChatBox.cs 164

public ChatBox(GUIComponent parent, bool isSinglePlayer) {   ....   arrowIcon = new GUIImage(....)   {     Color = new Color(51, 59, 46)   };   arrowIcon.HoverColor = arrowIcon.PressedColor =    arrowIcon.PressedColor = arrowIcon.Color;   ....   } 

Значение переменной *arrowIcon.PressedColor *присваивается самой себе. В то же время внутри класса *GUIIMage *содержится свойство SelectedColor. Скорее всего, разработчик хотел использовать его, но опечатался.

V3005 The ‘Penetration’ variable is assigned to itself. Attack.cs 324

public Attack(float damage,                float bleedingDamage,                float burnDamage,                float structureDamage,               float itemDamage,                float range = 0.0f,                float penetration = 0f) {    ....    Range = range;    DamageRange = range;    StructureDamage = LevelWallDamage = structureDamage;    ItemDamage = itemDamage;         Penetration = Penetration;                // <= } 

Ещё одна подобная ошибка. В этом случае программист хотел проинициализировать свойства объекта, но вместо параметра penetration присвоил свойству Penetration своё же значение.

V3025 Incorrect format. A different number of format items is expected while calling ‘Format’ function. Arguments not used: t.Character.Name. DebugConsole.cs 1123

private static void InitProjectSpecific() {   AssignOnClientRequestExecute("traitorlist",        (Client client, Vector2 cursorPos, string[] args) =>   {     ....     GameMain.Server.SendTraitorMessage(      client,       string.Format("- Traitor {0} has no current objective.",            // <=                    "",                                                   // <=                    t.Character.Name),                                    // <=      "",      TraitorMessageType.Console);      }); } 

Исходя из смысла использованной в методе GameMain.Server.SendTraitorMessage фразы, логично предположить, что спецификатор ввода {0} должен был содержать t.Character.Name. Однако же там окажется пустая строка.

Ошибка, скорее всего, является следствием неудачного copy-paste предыдущего использования метода GameMain.Server.SendTraitorMessage:

GameMain.Server.SendTraitorMessage(client,  "There are no traitors at the moment.", "", TraitorMessageType.Console); 

Возможно возникновение NullReferenceException

V3153 Enumerating the result of null-conditional access operator can lead to NullReferenceException. Voting.cs 181

public void ClientRead(IReadMessage inc) {   ....   foreach (GUIComponent item in            GameMain.NetLobbyScreen?.SubList?.Content?.Children)    // <=   {     if (item.UserData != null && item.UserData is SubmarineInfo)      {       serversubs.Add(item.UserData as SubmarineInfo);      }   }   .... } 

Если хотя бы один компонент из последовательности GameMain.NetLobbyScreen?.SubList?.Content?.Children будет равен null, то результат всего выражения тоже будет равен null. В таком случае будет выброшено исключение *NullReferenceException *при попытке перебора в foreach.

Подробно про использование оператора ?. в foreach можно прочитать в этой статье.

V3027 The variable ‘spawnPosition’ was utilized in the logical expression before it was verified against null in the same logical expression. LevelObjectManager.cs 274

private void PlaceObject(LevelObjectPrefab prefab,                           SpawnPosition spawnPosition,                           Level level, Level.Cave parentCave = null) {   float rotation = 0.0f;   if (   prefab.AlignWithSurface        && spawnPosition.Normal.LengthSquared() > 0.001f          // <=       && spawnPosition != null)                                 // <=   {     rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y,                                                     spawnPosition.Normal.X));   }   .... } 

Из кода видно, что сначала идёт вызов метода LengthSquared у поля Normal переменной spawnPosition и сравнение его с заданным значением, а затем переменная проверяется на null. Если spawnPosition будет равна null, то возникнет исключение NullReferenceException.

Простейшим решением будет поместить проверку на null в начало условия.

V3095 The ‘level’ object was used before it was verified against null. Check lines: 107, 115. BeaconMission.cs 107

public override void End() {   completed = level.CheckBeaconActive();                        // <=   if (completed)   {     if (Prefab.LocationTypeChangeOnCompleted != null)     {       ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);     }     GiveReward();     if (level?.LevelData != null)                               // <=     {       level.LevelData.IsBeaconActive = true;     }   } } 

Сначала значение level.CheckBeaconActive присваивается, а затем используется оператор ?. в выражении level?.LevelData. В этом случае возможны ситуации, когда level будет равен null — и будет выброшено исключение NullReferenceException либо level никогда не будет null — и проверка избыточна.

Выход за границы

V3106 Possibly index is out of bound. The ‘0’ index is pointing beyond ‘Sprites’ bound. ParticlePrefab.cs 303

public ParticlePrefab(XElement element, ContentFile file) {   ....   if (CollisionRadius <= 0.0f)      CollisionRadius = Sprites.Count > 0 ? 1 :                                            Sprites[0].SourceRect.Width / 2.0f; } 

При выполнении условия тернарного оператора значение переменной CollisionRadius станет равно 1. В противном случае значение Sprites.Count равно 0, и при обращении к первому элементу коллекции возникнет исключение IndexOutOfRangeException.

Ранее по коду встречается проверка: является ли коллекция пустой.

if (Sprites.Count == 0) {   DebugConsole.ThrowError($"Particle prefab \"{Name}\" in the file \"{file}\"                             has no sprites defined!"); } 

Однако блокирование выполнения дальнейшего кода не происходит. Разработчику следует пересмотреть условие тернарного оператора.

Лишние действия

V3107 Identical expression ‘power’ to the left and to the right of compound assignment. RelayComponent.cs 150

public override void ReceivePowerProbeSignal(Connection connection,                                               Item source, float power) {   ....   if (power < 0.0f)   {     ....   }   else   {     if (connection.IsOutput || powerOut == null) { return; }      if (currPowerConsumption - power < -MaxPower)     {       power += MaxPower + (currPowerConsumption - power);     }   } } 

Программист пытается прибавить переменной power переменную MaxPower и разницу между переменными currPowerConsumption и power. В разложенном варианте выражение будет иметь вид:

power = power + MaxPower + (currPowerConsumption - power); 

Выражение можно упростить, так как переменная power вычитается сама из себя. Упрощённый код будет выглядеть так:

power = MaxPower + currPowerConsumption; 

Всегда false

V3009 It’s odd that this method always returns one and the same value of ‘false’. FileSelection.cs 395

public static bool MoveToParentDirectory(GUIButton button, object userdata) {   string dir = CurrentDirectory;   if (dir.EndsWith("/")) { dir = dir.Substring(0, dir.Length - 1); }   int index = dir.LastIndexOf("/");   if (index < 0) { return false; }   CurrentDirectory = CurrentDirectory.Substring(0, index+1);    return false; } 

Странный метод, всегда возвращающий false. Возможно, здесь нет ошибки и так задумано, либо один из return должен возвращать true.

Утраченное значение

V3010 The return value of function ‘Trim’ is required to be utilized. GameServer.cs 1589

private void ClientWriteInitial(Client c, IWriteMessage outmsg) {   ....    if (gameStarted)   {     ....      if (ownedSubmarineIndexes.Length > 0)     {       ownedSubmarineIndexes.Trim(';');     }     outmsg.Write(ownedSubmarineIndexes);   } } 

Метод Trim не меняет значение ownedSubmarineIndexes, поэтому нет смысла вызывать его, не сохраняя результат. Правильный вариант:

ownedSubmarineIndexes = ownedSubmarineIndexes.Trim(';'); 

Заключение

PVS-Studio обнаружил ряд ошибок, опечаток и недочётов в исходном коде Baratrauma. Многие из них довольно легко допустить при разработке и оставить незамеченными на code-review.

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

О проверке других проектов с помощью статического анализатора PVS-Studio можно прочесть в нашем блоге.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Mikhail Evtihevich. Checking Barotrauma with the PVS-Studio static analyzer.


ссылка на оригинал статьи https://habr.com/ru/company/pvs-studio/blog/658361/