Анализатор обнаружил в тексте программы символы, которые могут ввести программиста в заблуждение. Эти символы могут не отображаться и изменять видимое представление кода в среде разработки. Комбинации таких символов могут привести к тому, что человек и компилятор будут интерпретировать код по-разному.
Это может быть сделано специально. Такой вид атаки называется Trojan Source. Подробнее:
Анализатор выдаст предупреждение, если найдет один из следующих символов:
Обозначение |
Код |
Название |
Описание |
---|---|---|---|
LRE |
U+202A |
LEFT-TO-RIGHT EMBEDDING |
Текст после символа LRE интерпретируется как вставленный и отображается слева направо. Действие LRE прерывается символом PDF или символом перевода строки. |
RLE |
U+202B |
RIGHT-TO-LEFT EMBEDDING |
Текст после символа RLE интерпретируется как вставленный и отображается справа налево. Действие RLE прерывается символом PDF или символом перевода строки. |
LRO |
U+202D |
LEFT-TO-RIGHT OVERRIDE |
Текст после символа LRO принудительно отображается слева направо. Действие LRO прерывается символом PDF или символом перевода строки. |
RLO |
U+202E |
RIGHT-TO-LEFT OVERRIDE |
Текст после символа RLO принудительно отображается справа налево. Действие RLO прерывается символом PDF или символом перевода строки. |
|
U+202C |
POP DIRECTIONAL FORMATTING |
Символ PDF прерывает действие одного из символов LRE, RLE, LRO или RLO, встреченного ранее. Прерывает ровно один, последний из встреченных, символ. |
LRI |
U+2066 |
LEFT‑TO‑RIGHT ISOLATE |
Текст после символа LRI отображается слева направо и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие LRI прерывается символом PDI или символом перевода строки. |
RLI |
U+2067 |
RIGHT‑TO‑LEFT ISOLATE |
Текст после символа RLI отображается справа налево и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие RLI прерывается символом PDI или символом перевода строки. |
FSI |
U+2068 |
FIRST STRONG ISOLATE |
Направление текста после символа FSI задается первым управляющим символом, не входящим в этот фрагмент текста. Другие управляющие символы не влияют на отображение этого текста. Действие FSI прерывается символом PDI или символом перевода строки. |
PDI |
U+2069 |
POP DIRECTIONAL ISOLATE |
Символ PDI прерывает действие одного из символов LRI, RLI или FSI, встреченного ранее. Прерывает ровно один, последний из встреченных, символ. |
LRM |
U+200E |
LEFT-TO-RIGHT MARK |
Текст после символа LRM отображается слева направо. Действие LRM прерывается символом перевода строки. |
RLM |
U+200F |
RIGHT-TO-LEFT MARK |
Текст после символа RLM отображается справа налево. Действие RLM прерывается символом перевода строки. |
ALM |
U+061C |
ARABIC LETTER MARK |
Текст после символа ALM отображается справа налево. Действие ALM прерывается символом перевода строки. |
ZWSP |
U+200B |
ZERO WIDTH SPACE |
Неотображаемый пробельный символ. Использование символа ZWSP привести к тому, что разные строки будут отображаться одинаково. Например, 'str[ZWSP]ing' отображается как 'string'. |
Рассмотрим следующий фрагмент кода:
#include <iostream>
int main()
{
bool isAdmin = false;
/*[RLO] } [LRI] if (isAdmin)[PDI] [LRI] begin admins only */ // (1)
std::cout << "You are an admin.\n";
/* end admins only [RLO]{ [LRI]*/ // (2)
return 0;
}
Изучим детально строку (1).
[LRI] if (isAdmin)[PDI]
Здесь символ [LRI] действует до символа [PDI]. Строка 'if (isAdmin)' будет отображаться слева направо и считается изолированной, получаем 'if (isAdmin)'.
[LRI] begin admins only */
Здесь символ [LRI] действует до конца строки. Получаем изолированную строку: 'begin admins only */'
[RLO] {пробел1}, '}', {пробел2}, 'if (isAdmin)', 'begin admins only */'
Здесь символ [RLO] действует до конца строки и отображает текст справа налево. Каждая из полученных в предыдущих пунктах изолированных строк рассматривается как отдельный неделимый символ. Получаем такую последовательность:
'begin admins only */', 'if (isAdmin)', {пробел2}, '{', {пробел1}
Обратите внимание, что символ закрывающей фигурной скобки теперь отображается как '{' вместо '}'.
Итоговый вид строки (1), который может быть отображен в редакторе:
/* begin admins only */ if (isAdmin) {
Похожие преобразования затронут и строку (2), которая отобразится так:
/* end admins only */ }
Финальный вид кода, который может отобразиться в редакторе:
#include <iostream>
int main()
{
bool isAdmin = false;
/* begin admins only */ if (isAdmin) {
std::cout << "You are an admin.\n";
/* end admins only */ }
return 0;
}
Ревьювер может посчитать, что в коде выполняется некоторая проверка перед выводом сообщения. Он проигнорирует комментарии и подумает, что код должен выполняться так:
#include <iostream>
int main()
{
bool isAdmin = false;
if (isAdmin) {
std::cout << "You are an admin.\n";
}
return 0;
}
Однако, на самом деле, проверки нет. Для компилятора рассмотренный код выглядит так:
#include <iostream>
int main()
{
bool isAdmin = false;
std::cout << "You are an admin.\n";
return 0;
}
Теперь рассмотрим более простой и в то же время более опасный пример использования неотображаемых символов:
#include <string>
#include <string_view>
enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };
constexpr BlockCipherType
StringToBlockCipherType(std::string_view str) noexcept
{
if (str == "AES[ZWSP]")
return BlockCipherType::AES;
else if (str == "TripleDES[ZWSP]")
return BlockCipherType::TripleDES;
else
return BlockCipherType::DES;
}
Функция 'StringToBlockCipherType' производит конвертацию строки в одно из значений перечисления 'BlockCipherType'. По коду можно сделать вывод, что функция возвращает три разных значения, однако это не так. Из-за того, что в конце каждого строкового литерала дописан неотображаемый пробельный символ [ZWSP], проверки на равенство со строками 'AES' и 'TriplesDES' будут ложными. В итоге из трех ожидаемых возвращаемых значений функция будет возвращать лишь 'BlockCipherType::DES'. В то же время код в редакторе может отображаться следующим образом:
#include <string>
#include <string_view>
enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };
constexpr BlockCipherType
StringToBlockCipherType(std::string_view str) noexcept
{
if (str == "AES")
return BlockCipherType::AES;
else if (str == "TripleDES")
return BlockCipherType::TripleDES;
else
return BlockCipherType::DES;
}
Если анализатор выдал предупреждение о неотображаемых символах на вашем коде, включите отображение невидимых символов в вашем редакторе и убедитесь, что они не изменяют логику выполнения программы.
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V1076. |