Почему base64 превращается в Jase64

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

 читать первым в телеграм    читать первым в макс

Выполняем команду:

echo Shs | base64 -d
Jase64: invalid input

И наблюдаем аномалию в названии утилиты: Jase64.

Из команды понятно, что утилита base64 при декодировании обнаружила ошибку в данных. Ошибка — некорректная длина закодированного текста (invalid input).

Но чо за херня с Jase64? Новая попа-группа Юры Шильникова-Томатного? Должно же быть Base64?

Если что-то пошло попесде, нам поможет «страус». Расчехляем strace.

Давай посмотрим что и куда пишет base64. Перенаправляем стандартные потоки вывода и ошибки в устройство /dev/null. Дополнительно говорим «страусу», чтобы выводил системные вызовы write.

Запускаем кишку:

С запущенной кишкой, обычно к проктологу.

LC_ALL=C strace -Yqqqyfe write -P /dev/null --signal=none bash -c 'echo Shs | base64 -d &>/dev/null'

Кратенько по ключам:

  • LC_ALL = страхуемся
  • Y = чекаем вызовы ввода-вывода
  • qqq = меньше мусора
  • y = показываем пути к файлам
  • f = чекаем дочерние процессы
  • e = выбираем вызовы write
  • P = ограничение трассировки
  • signal = не следим за сигналами

После запуска получаем:

[<base64>] write(1</dev/null>, "J\33", 2) = 2
[<base64>] write(2</dev/null>, "base64: ", 8) = 8
[<base64>] write(2</dev/null>, "invalid input", 13) = 13
[<base64>] write(2</dev/null>, "\n", 1) = 1

Уже поинтереснее. Разбираем.

  • В первой строке происходит запись части декодированного текста в стандартный вывод (дескриптор 1).
  • В следующих двух строках, в стандартный поток ошибок пишется имя утилиты и ошибка (дескриптор 2).
  • Ну и последняя строка, запись новой строки.

Теперь снова смотрим первую строку и видим что системный вызов write пишет букву «J» и какой-то магический символ 33.

И чо? А то, что для драйвера терминала, этот символ означает начало управляемой последовательности. Вот это поворот!

Скучная теория

Драйвер терминала — это код, который распознает управляющие последовательности и интерпретирует их в действия. Например, перемещает курсор.

В моем случае, драйвер это эмулятор терминала (Terminal). А еще есть код в пространстве ядра, который реализует специальные устройства. И этот код может модифицировать данные, проходящие через эту линию связи.

Схема терминала:

Bash write stdout -> /dev/pts/number Kernel_Space <- /dev/ptmx Terminal read

Terminal write -> /dev/ptmx Kernel_Space <- /dev/pts/number Bash read

Bash read это чтение команды, или сочетания клавиш например для редактирования строки.

Возвращаемся к Jase64

Для начала исключаем пространство ядра (Kernel_Space) и проводим проверку данных, которые поступили на устройство читаемое эмулятором терминала.

Видим, что на устройстве эмулятора Terminal, данные пришли без искажений. Подозреваемым остается драйвер терминала.

write(30</dev/ptmx>, "\33[200~echo Shs | base64 -d \33[201"..., 33) = 33

Как это получить, я рассказал утром в этом посте.

И происходит следующее. Эмулятор терминала, получив символ «J», отображает его, но потом натыкается на специальный символ ESC — начало управляющей последовательности и слово base64.

Символ «b» распознаётся как часть управляющей последовательности и этот символ не отображается.

Многие терминалы не имеют никаких действий связанных с данной последовательностью «ESCb» и она просто отбрасывается.

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

echo -ne 'J\033' ; sleep 3 ; echo base64 >&2
echo -ne 'J\033[64' ; sleep 3 ; echo bashdays >&2
Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Вот такие вот приколы, вот такие вот кишочки. Нельзя просто так взять и насыпать в терминал всё что тебе захочется.

Если после каких-то действий, ты начал замечать аномалии, выполни команду reset и всё встанет на свои места.

Хорошая статья: The TTY demystified.

И да, всем отличных предстоящих выходных, берегите себя.

Комментарии