Чем хорош и плох «безопасный DNS»
Возможно вы слышали, что в новых версиях Firefox внедрили поддержку нового шифрованного/безопасного/еще более лучшего (нужное – подчеркнуть) DNS, но не знаете что это за DoH такой и нужeн ли он вам; попробую объяснить.

Сперва – что такое старый нешифрованный, небезопасный и менее лучший DNS. Совсем кратко – это «адресная книга» для веб-сайтов: мы набираем в адресной строке браузера pikabu.ru, но браузер не знает, что это и как это найти. Он посылает DNS-запрос к DNS-серверу, и тот уже объясняет: что, как и где. Подробнее можно почитать в Педивикии – тот редкий случай, когда все объясняется четко и по делу.
Одна из проблем DNS заключается в том, что поиски нужного сайта происходят по незашифрованному соединению. Соответственно, все сервера, через которые проходят эти поиски, видят кто и что ищет. Если один из таких серверов контролируется злоумышленниками, чрезмерно «любознательными» компаниями или кровавой гэбней™, то возможны два вектора атаки на пользователя: подмена цели и сбор «телеметрии».
Подмена цели, это когда вы хотите зайти на pikabu.ru, а попадаете на сборник троянов. Или когда набираете адрес любимого сайта с детским проном котятками, а вместо него видите «Доступ к ресурсу заблокирован Роскомпозором». В обоих случаях происходит перехват DNS-запроса и подмена IP-адреса искомого сайта. С «телеметрией», думаю, и так все понятно – обычная история из серии «хочу все знать» и желательно про всех.
Если же DNS-запрос зашифровать, то ваши намерения в Интернете окажутся неизвестны «посредникам», т.е. в ваших поисках IP-адреса сайта pikabu.ru они будут участвовать не зная об этом. Куда идете вы с Пятачком Огнелисом будет известно лишь вам, администрации Пикабу и используемого DNS-сервера, а не всем подряд по дороге к нему и обратно.
Правда евангелисты «безопасного DNS» часто «забывают» уточнить несколько моментов.
Во-первых, вся эта возня с настройками браузера на «безопасный DNS» имеет смысл только в том случае, если вы собираетесь заходить на сайты, которые поддерживают соединение по защищенному протоколу – HTTPS, в противном случае вы получите лишь бронированную калитку в картонных воротах.
Во-вторых, первый запрос к искомому сайту будет содержать незашифрованный идентификатор имени сервера (SNI), из которого будут четко видны ваши намерения. Это нормально, как говорится, by design, но если провайдер использует систему анализа пакетов (DPI), то все напрасно. Чтобы превозмочь провайдерский DPI, вы можете включить поддержку шифрования идентификатора – ESNI (Encrypted SNI): заходите в about:config, находите (или создаете) параметр network.security.esni.enabled (тип – Boolean), и выставляете ему значение true, но…
Но, в-третьих, если провайдер использует DPI, то не обнаружив в вашем запросе ожидаемого там SNI, он, вполне вероятно, просто заблокирует этот запрос и вы увидите вместо искомого сайта сообщение PR_CONNECT_ABORTED_ERROR. Да, это цензура, не имеющая правовых оснований, нарушение положений ФЗ «О связи», прав человека, гражданина, потребителя и это все – жалуйтесь в Роскомпозор, там вас с интересом выслушают и даже посочувствуют.
В-четвертых, если в реестр Роскомпозора внесено не доменное имя, а IP-адрес, то схема с шифрованием DNS-запроса и SNI просто не работает (и не должна).
Таким образом, «безопасный DNS» панацея не столько от цензуры, сколько от излишне любознательных «информационных посредников», в первую очередь – интернет-провайдеров, и интернет-жулья. Но… но пока где-то в сибирской тайге или московском пентхаузе горько рыдает отечественное жулье, отлученное от ваших интернет-логов, в солнечной Калифорнии плачет от счастья сотрудник Cloudflare или другой Корпорации бобра, которой вы добровольно передали отнятые у своего провайдера данные о своих интернет-маршрутах. Упс…
Это было в-пятых? Погнали дальше, в-шестых: если вы блокируете рекламу и прочий мусор с помощью hosts, блокировка работать перестанет, т.к. DNS-клиент операционной системы будет получать от браузера уже зашифрованный запрос к DNS-серверу и проверить наличие искомого сайта в hosts не сможет. Вернее, сможет в теории, но не умеет в разбор пакетов, вычленение из них SNI и сравнение его с записями в hosts, а переписывать код ОС ради этого никто не будет (хотя насчет Linux утверждать не стану).
Как в теории можно было бы реализовать максимально безопасный и удобный для пользователя шифрованный DNS? Я это вижу как «DNS-рулетку»: у пользователя существует список безопасных DNS-серверов, которым он доверяет (т.е. в нем априори нет серверов Google и прочих глобальных шпионов), при подаче DNS-запроса из этого списка случайным образом выбирается один из серверов, к нему и идет запрос.
Следующий запрос – к другому случайному серверу из списка, в который могут входит сотни серверов. Таким образом, каждый из серверов получает некоторые данные о запросах (читай – идентифицирующих признаках) пользователя, но ни один из них не видит всей картины целиком. В каждом запросе еще можно подставлять рандомный user-agent и прочие составляющие «цифрового отпечатка». Мечтать не вредно…
Для справки
Доведенных до практической реализации) протоколов безопасного DNS существует два: DoT (DNS over TLS) и DoH (DNS over HTTPS). Первый предложен Институтом информационных наук Университета Южной Калифорнии, компанией Verisign и ICANN в 2016 году, второй – спустя два года – той же ICANN и компанией Mozilla, оба до сих пор не утверждены в качестве стандарта.
DoT поддерживается на системном уровне в Android 9 и выше, DoH – в экспериментальных функциях Chrome (начиная с версии 78) и полноценно в Firefox (с версии 62). ESNI, насколько я знаю, сейчас поддерживается только в Firefox.
Адресация и многозадачность: Механизм защиты
Во всех процессорах, начиная с Intel286, при установке CR0.PE = 1 включается защищенный режим работы. В этом режиме помимо средств расширенной адресации и поддержки мультизадачности процессор обеспечивает многоуровневую систему защиты. Защита предотвращает несанкционированное использование данных, исполняемых кодов и портов ввода/вывода. Таким образом обеспечивается устойчивая и безопасная работа всех программ в многозадачной среде.
В функции механизма защиты входят следующие действия, выполняемые всякий раз при обращении к данным или программам:
- проверка возможности выхода за пределы сегмента (размер каждого сегмента кода или данных задается в его дескрипторе);
- проверка соответствия типов используемых сегментов тому, по какому назначению эти сегменты используются (в зависимости от типа сегмента возможны различные варианты обращения к нему);
- проверка соответствия уровней привилегий (разрешение/запрещение затребованных действий в зависимости от текущего уровня привилегий и установленных правил доступа к коду или данным);
- проверка адресуемой области (предотвращение несанкционированного доступа к защищенным областям памяти);
- проверка точек входа (предотвращение несанкционированного вызова защищенных программ);
- проверка команд (предотвращение выполнения привилегированных команд на низких уровнях привилегий).
Для обеспечения работы всех этих функций механизм защиты использует специальные поля и флаги в различных регистрах и управляющих структурах данных:
- флаг Sдескриптора сегмента,
- поле TYPEдескриптора сегмента,
- поле LIMITдескриптора сегмента,
- флаг Gдескриптора сегмента,
- флаг Eдескриптора сегмента,
- поле DPLдескриптора сегмента,
- поле RPL (CPL) селектора сегмента,
- флаги U/S и W/R в каталогах и таблицах страничных преобразований,
- флаг CR0.WP,
- поле уровня привилегий ввода/вывода в регистре флагов EFLAGS.IOPL.
Правильное заполнение и отслеживание значений этих полей и флагов позволяет создать многоуровневую, стойкую к ошибкам и злонамеренным действиям систему защиты программ и данных.
Существует два уровня работы системы защиты: защита на уровне сегментов и защита на уровне страниц. Оба механизма не имеют каких-либо специальных средств для их включения/выключения и начинают функционировать сразу, как только процессор оказывается в соответствующем режиме работы (для защиты на уровне сегментов — в защищенном режиме, для страничной защиты — при включении страничного механизма).
Защита на уровне сегментов
Защита на уровне сегментов является основной и обеспечивает наибольшее количество функций. Основа ее функционирования — это разделение уровней привилегий и отслеживание текущих прав доступа.
Система защиты на уровне сегментов поддерживает четыре уровня привилегий (0, 1, 2, 3). Самым привилегированным уровнем считается нулевой уровень привилегий. На этом уровне предполагается исполнение программ ядра мультизадачных операционных систем (в простых ОС все программы, в т.ч. и пользовательские, могут исполняться на нулевом уровне привилегий, что равнозначно отсутствию защиты по уровням привилегий). Следующие уровни привилегий (1, 2, 3) предназначены для вспомогательных и пользовательских программ. Специальные дескрипторы — т.н. шлюзы, обеспечивают взаимодействие между задачами, выполняемыми на различных уровнях привилегий. Обычно все пользовательские программы работают на уровне привилегий 3, т.е. обладают наименьшим количеством прав доступа к различным компонентам ОС и областям памяти.
Для слежения за текущим уровнем привилегий (Current Privilege Level – CPL) и разграничения прав доступа предназначены поля DPL в дескрипторах сегментов и RPL в селекторах сегментов. Текущий уровень привилегий CPL соответствует уровню привилегий текущего сегмента кода и может быть считан из его селектора в регистре CS. Каждый раз при обращении к командам и данным процессор, опираясь на значение CPL, проводит проверку допустимости того или иного действия. В случае несоответствия запрошенного действия правам выполняемой программы, процессор генерирует специальное прерывание #GP (ошибка общей защиты), и далее разрешение возникшей проблемы ложится на обработчик этого прерывания (который, должен функционировать на самом привилегированном уровне).
Подробно все проводимые проверки и действия механизма сегментной защиты при выполнении тех или иных команд приведены в описаниях каждой команды. Функциональное назначение и некоторые аспекты использования всех флагов и полей, задействованных механизмом защиты, приведены при описании соответствующих регистров и структур данных.
Защита на уровне страниц
Механизм страничной защиты введен в процессорах начиная с Intel386 и работает поверх всех средств сегментной защиты. Он включается в работу при инициализации страничного механизма и обеспечивает:
- защиту системных областей памяти от доступа туда пользовательских программ,
- защиту страниц с программным кодом от изменения.
Его работа основана на специальных полях U/S и W/R в элементах таблиц страничных преобразований.
Поле U/S определяет права доступа для соответствующей страницы (страниц):
- U/S = 0 — Supervisor (Супервизор),
- U/S = 1 — User (Пользователь).
Процессор защищает страницы супервизора от доступа к ним из программ с привилегиями пользователя. То есть происходит расширение механизма сегментной защиты на страничную схему. Программы с текущим уровнем привилегий обладают правами супервизора, а программы с — правами пользователя.
Поле W/R предназначено для установки защиты пользовательских страниц от записи. Причем в зависимости от текущего значения флага CR0.WP пользовательские страницы могут быть защищены и от записи программами супервизора, а не только от пользовательских программ.
Поля U/S и W/R имеются и в элементах каталога страниц и в элементах таблицы страниц. Поэтому возможно задание комбинированных условий защиты для различных страниц.
Таблица 2.6. Комбинация защиты в каталоге и в таблице страниц
Особенности адресации в защищенном режиме
Защищенный режим работы позволяет использовать все возможности, предоставляемые современным микропроцессором. Все современные многозадачные операционные системы работают только в этом режиме.
Реальный режим поддерживает выполнение всего одной программы. Для этого достаточно простых механизмов распределения оперативной памяти и нет потребности в организации защиты программы от влияния других программ. Все, что нужно знать программе, — это адреса, по которым располагаются сегменты кода, данных и стека. Если возникает потребность в размещении в программно-аппаратной среде нескольких независимых программ, то автоматически встает вопрос об их защите от взаимного влияния. И процессор переходит в защищенный режим работы.
В отличие от реального режима, в защищенном режиме программа уже не может запросто обратиться по любому физическому адресу. В защищенном режиме используется виртуализация памяти (страничная модель). Каждой загруженной программе (задаче, процессу) операционная система выделяет 4 Гбайт виртуальной памяти, которая состоит из сегментов различного назначения и с разными правами доступа. Сегмент может иметь почти произвольный размер до 4 Гбайт, в отличие от сегмента реального режима, который не превышает 64 Кбайт. Из одних сегментов можно только читать данные, в другие возможна и запись.
Ключевым объектом защищенного режима является специальная структура – дескриптор сегмента, содержащий краткое описание непрерывной области памяти, которая может являться сегментом кода, данных или стека. Все дескрипторы программ, выполняемых в текущий момент, собираются в одну из трех дескрипторных таблиц.
Для программного кода выделяются специальные сегменты, команды могут выбираться и исполняться только из них. Процессору «безразлично» содержимое ячейки памяти, которой передается управление, — он всегда пытается трактовать ее как код команды. Если ошибочно управление передалось на сегмент данных, то сработает защита и ошибочный процесс будет завершен.
Виртуальная логическая память, адресуемая программой в пределах выделенных ей сегментов, разбивается на страницы. В системах Win32 с процессорами Pentium размер одной страницы составляет 4 Кбайт, следовательно, Win32 разбивает блок памяти 4 Гбайт на страницы по 4 Кбайт. При этом в каждой странице содержится небольшой объем служебной информации, в частности, данные о том, занята страница или нет.
В служебную информацию страницы входит ссылка на таблицу перевода страниц. Эта таблица связывает отдельную виртуальную страницу программы с реальной страницей, доступной в ОЗУ. Таким образом операционная система выполняет перевод виртуального адреса в реальный (физический) адрес ОЗУ.
Если активировано несколько программ, то в физической оперативной памяти в каждый момент времени присутствует только часть виртуальных страниц. Остальные страницы хранятся на диске, откуда операционная система может «подкачать» их в физическую память, предварительно выгрузив на диск часть не используемых в данный момент страниц. Обращение процессора к ячейке виртуальной памяти, присутствующей в физической памяти, происходит обычным образом. Если затребованная область памяти в данный момент отсутствует в физической памяти, то операционная система организует замену страниц, называемую свопингом (swapping).