Привет, %username%! Довольно популярный вопрос на собеседовании, который задаю в том числе и я. У меня этот вопрос звучит дословно вот так: “Что будет, если выполнить в консоли из под root’a chmod a-x $(which chmod) и как это починить?”

Когда мне впервые задали на собесе этот вопрос, я придумал 2 самых очевидных способа починить этот косяк – думаю их должны либо знать все, либо догадаться до этих способов (о них расскажу позже). А для начала расскажу что тут происходит:

  • в первую очередь оболочка “вычисляет значение в скобочках”, а там у нас вызов команды which с аргументом chmod, который возвращает путь до исполняемого файла (в нашем случае chmod) основываясь на переменной окружения PATH и показывает первое нахождение;
  • дальше запускается утилита chmod, которая отвечает за назначение прав доступа (или ACL) к файлам, которой в качестве первого аргумента передается a-x, а в качестве второго путь до исполняемого файла ‌/bin/chmod. Первый аргумент нам говорит о то, что “для всех битов доступа” (a это короткая запись ugo, что является сокращением от user/group/other – “пользователь владелец”/“группа владельца”/“остальные”) нужно “отнять” (- это отобрать биты доступа, а + добавить биты доступа) бит доступа на “исполнение” (x - executable, r - read, w - write/edit);

Примечание: утилита which с помощью флага -a может отобразить абсолютно все найденные пути, а с помощью флага -s просто проверить существование (хотя бы один доступный путь) – завершение с exit code 0 говорит о том, что исполняемый файл найден, а завершение с exit code 1 говорит о том, что исполняемый файл не найден.

Собственно это все, что происходит в строке chmod a-x $(which chmod). А теперь попробуем придумать, как можно это исправить.

Попытки исправить

Переложить содержимое файла

Первое, что должно приходить на ум:

  • найти файл, который имеет execution bit и выполнить команду cat $(which chmod) > /path/to/file_with_executable_bit;
  • после чего, можно будет сделать /path/to/file_with_executable_bit a+x $(which chmod);

Да, вот так легко и просто можно исправить свой косяк (или не свой). Так же локально можно использовать rsync --chmod=777 – да, rsync умеет выставлять принудительно права на файлы.

Скопировать атрибуты

Стандартная утилита cp умеет перекладывать не только сами файлики, но и переносить отдельно их атрибуты:

cp --attributes-only --preserve=mode /bin/chown /bin/chmod

Скопировать с другого сервера

Наверняка, кто-то скажет (и это самый популярный почему-то вариант): скопировать с другой системы. И это можно сделать двумя способами:

  • с помощью утилиты scp с флагом -p;
  • с помощью утилиты rsync с флагами -av;

Второй вариант – rsync – более предпочтителен, т.к. сохранит еще и пользователя и группу, а scp этого не сделает. Но учитывая, что в рамках задачки мы работаем под root’ом – можно считать эти варианты идентичными.

Однострочники на ЯПах

Да, этот способ – это очередное подтверждение того, что ops должен уметь кодить (хотя бы на уровне говнокода) – о том, почему это надо более подробно я упоминал тут.

Собственно к решению - выполняем вот такую грязь:

  • на python3: python3 -c "import os; os.chmod('/bin/chmod', 0o755)"
  • на python2: python2 -c "import os; os.chmod('/bin/chmod', 0755)"
  • на perl: perl -e "chmod 0755, "/bin/chmod"; exit;
  • на php: php -r 'chmod("/bin/chmod", 0755);'
  • на ruby: ruby -r fileutils -e "FileUtils.chmod 0755,'/bin/chmod'"

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

Golang

На Golang можно сделать таким же простым способом – положи код из примера ниже в файл chmodfixer.go:

// chmodfixer.go
package main

import (
    "log"
    "os"
)

func main() {
    var perm fs.FileMode
    perm = 0755
    file := "/bin/chmod"
    err := os.Chmod(file, perm)
    if err != nil {
        log.Fatalf("can't set file permissions for %s with err: %s", file, err)
    }
    log.Printf("file permissions for %s is set to %d", file, perm)
}

Выполнение в консоли go run fix.go просто запустит данный “скрипт”, а go build -o /tmp/chmodfixer fix.go скомпилирует данный код, а исполняемый файл сохранит в /tmp/chmodfixer.

Midnight Commander aka mc

Не особо любимый мною “навигатор по файловой системе” может пригодиться в решении данной проблемы – запустим его в нужной директории mc /bin. Надо всего лишь пройти в меню Press F9 -> File -> Chmod, а дальше выставить все права – читай внимательно и все будет норм. Если проделать все те же самые действия, но на горячих клавишах – будет вот такая комбинация:

  • C-s chmodC-s == Ctrl+s найти файл;
  • C-x c rwxdbhsC-x c == Ctrl+x c назначить права;

Редакторы Emacs и VIM

Да, можно использовать редакторы и для на столько неожиданных вещей. Начнем с Emacs и запустим его – emacs /bin, а дальше:

  • C-s chmod;
  • M 755

А на VIM’e можно сделать в одну строку вообще, вот так – ‌vim -e --cmd "call setfperm('/bin/chmod','rwxr-xr-x')|q".

Однострочник на тонких сях

Отдельно стоит упомянуть про tcc – tiny C compiler. Не самый очевидный выбор, но как вариант решения проблемы – бронебойный. Сделать можно вот так – буквально в одну строку (поставим права 0755):

tcc -run <(echo '#include <sys/stat.h>'; echo '#define CHMODFILE "/bin/chmod"'; echo 'void main() { chmod(CHMODFILE, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); }')

Изучить, как определить права можно в исходных кода библиотеки sys/stat.h на GitHub в репозитории torvalds/linux.

Reinstall package

Можно переустановить пакет, который содержит нужный нам бинарник – при установке всегда назначаются правильные/корректные права на файлы из пакета. Сделать можно вот так:

  • RHEL-based системы: dnf reinstall $(rpm -qf /bin/chmod);
  • Debian-based системы: apt reinstall $(dpkg-query -S bin/chmod|cut -d: -f1)

LD SO

Можно задействовать некоторые библиотеки, например на Ubuntu 20.04 с архитектурой x86_64 можно сделать вот такой финт ушами:

sudo /lib64/ld-linux-x86-64.so.2 /bin/chmod 755 /bin/chmod

Архиватор tar

Внезапный кандидат – архиватор tar – решает проблему буквально в одну строку:

tar -cP --mode=755 /bin/chmod | tar x

Для разнообразия

Стоит так же упомянуть про такие штуки:

  • ctypes.sh – небольшая библиотека, написанная на чистом C, которая позволит в bash-функциях делать некоторые вещи, в том числе и назначать права на файлы;
  • gdb – GNU Linux Debugger, с помощью которого можно так же дернуть нужный syscall и выставить нужные права;

На форуме Linux Questions собраны самые разнообразные способы решения – большая часть пересекается с данной статьей.

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

Итого

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

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


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