Libseccomp: RFE: различать неизвестные системные вызовы

Созданный на 16 авг. 2020  ·  18Комментарии  ·  Источник: seccomp/libseccomp

Инициировано обсуждением (в июне и августе ) на systemd-devel ..

systemd-nspawn предпочитает возвращать EPERM для системных вызовов, не внесенных в белый список. Однако это вызывает проблемы в таких случаях, как openat2 , когда libc проверяет наличие ENOSYS и возвращается к другой реализации.

Мне кажется, что «в основном правильное» решение могло бы состоять в том, чтобы проверить, попадает ли номер системного вызова в диапазон определенных системных вызовов, существовавших во время создания seccomp. Я уверен, что есть крайние случаи (я знаю, что некоторые арки делают странные вещи), но если инструменты, которые анализируют syscalls.csv т. д., могут генерировать простой #define для максимального известного номера системного вызова, который может быть полезный?

enhancement prioritmedium

Самый полезный комментарий

Основываясь на приведенном выше обсуждении, похоже, что большинство (все?) людей здесь считают, что проблема № 11 - это правильный способ решить эту проблему; есть ли у кого-нибудь проблемы с закрытием этой проблемы в пользу переноса будущего обсуждения на исходную проблему (№ 11)?

Все 18 Комментарий

Привет @srd424. Я хочу убедиться, что понимаю, о чем вы просите в этой проблеме ... мне кажется, что вы в основном хотели бы знать, «знает» ли libseccomp о данном системном вызове, независимо от того, реализован ли этот конкретный системный вызов в этой архитектуре /АБИ, да?

Если это так, я считаю, что вы должны иметь возможность использовать seccomp_syscall_resolve_name(...) для получения нужной информации. Если возвращаемое значение __NR_SCMP_ERROR то системный вызов неизвестен libseccomp, если он положительный, то системный вызов существует в собственной архитектуре/ABI, а если отрицательный, то системный вызов не существует в собственной архитектуре/. АБИ. Это работает для вас?

Это может сделать фильтры... многословными!

Что я надеялся сделать, так это иметь правило фильтрации, сравнивающее номер системного вызова с самым высоким известным, и, если он больше, вернуть ENOSYS , в противном случае вернуть EPERM (при условии, что системные вызовы из белого списка были обработаны по предыдущему правилу.)

Однако, глядя на подробности для seccomp_rule_add , я не уверен, что это сработает ... номер системного вызова обрабатывается особым образом. Предположительно, для этого можно было бы создать необработанный фильтр bpf, но это будет означать некоторые более агрессивные изменения в libseccomp - вероятно, выше моего уровня оплаты!

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

Это может сделать фильтры... многословными!

Я не совсем уверен, что вы имеете в виду здесь ...? Вызов seccomp_syscall_resolve_name(...) самом деле не влияет на фильтр, он просто запрашивает внутреннюю базу данных системных вызовов libseccomp для разрешения системных вызовов. Вы можете вызвать его один раз, тысячу раз или никогда, и ваш фильтр будет точно таким же :)

Что я надеялся сделать, так это иметь правило фильтрации, сравнивающее номер системного вызова с наибольшим известным, и если он больше, возвращать ENOSYS, в противном случае возвращать EPERM (при условии, что системные вызовы из белого списка были обработаны более ранним правилом).

Ладно, кажется, я начинаю понимать, о чем вы просите. Вы хотите, чтобы сам фильтр, а не код приложения, выполнял определенные действия (возвращал ENOSYS в приведенном выше примере), если системный вызов неизвестен libseccomp? Это в принципе так или я опять что-то упускаю?

Этот комментарий во втором треде, упомянутом выше, заставил меня улыбнуться :+1:

Я попытался открыть обсуждение обработки ENOSYS в libseccomp на
https://github.com/seccomp/libseccomp/issues/286 , но я, вероятно, не
быть очень последовательным..

Прочитав упомянутые вами темы, я думаю, что нахожусь на той же странице.

Если кто-то (libseccomp, nspawn, кто угодно) может вернуть ENOSYS , тогда glibc попытается перейти от более нового системного вызова, например, openat2 , к более старому системному вызову, например, openat . Возврат EPERM в glibc просто заставляет glibc думать, что вызов не разрешен, и glibc сдается. Это справедливая переформулировка первоначального комментария в этом вопросе?

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

Спасибо за РЭФ.

Если кто-то (libseccomp, nspawn, кто угодно) может вернуть ENOSYS , тогда glibc попытается перейти от более нового системного вызова, например, openat2 , к более старому системному вызову, например, openat . Возврат EPERM в glibc просто заставляет glibc думать, что вызов не разрешен, и glibc сдается. Это справедливая переформулировка первоначального комментария в этом вопросе?

Да, это звучит правильно. Мнение людей из systemd заключается в том, что EPERM в большинстве случаев разумен для системных вызовов, занесенных в черный список, предположительно, поскольку он передает конечному пользователю / администратору «не разрешено». Отсюда идея различать «новые» и «старые» системные вызовы и использовать ENOSYS для всего нераспознанного. Я предполагаю, что мы не хотим перечислять и тестировать каждый отдельный системный вызов в BPF из соображений производительности, поэтому отслеживание верхней отметки для известных номеров системных вызовов для каждой архитектуры казалось «максимальным усилием».

Было бы интересно узнать, что docker, podman, lxc и т. д. делают со своей фильтрацией seccomp, чтобы узнать, выиграют ли они. Тем временем я прорекламировал патч для nspawn, который позволял регистрировать события seccomp, что немного облегчило бы отладку.

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

Спасибо за РЭФ.

Я согласен с @drakenclimber , эта просьба звучит разумно, я думаю, мне просто нужно еще немного времени, чтобы подумать о возможных решениях :)

На довольно базовом уровне это похоже на RFE # 11, и, в конце концов, это может быть самый простой способ реализовать это таким образом, который не страшен для приложений: приложение может указать максимальную поддерживаемую версию API ядра, например v5.8 (очевидно, токенизированный), а также данное действие для всего, что за его пределами, а затем libseccomp обрабатывает все остальное. Это сработает для вас, ребята, @srd424?

Привет, это также обсуждалось в https://github.com/systemd/systemd/pull/16739.

приложение может указать максимальную поддерживаемую версию API ядра, например, v5.8 (очевидно, токенизированную), а также данное действие для чего-либо еще, а затем libseccomp обрабатывает все остальное.

Это отлично сработает. В systemd/systemd-nspawn мы хотели бы вернуть пользовательские errno для любых явно разрешенных и запрещенных системных вызовов, EPERM для любых других в «поддерживаемой версии API ядра» и ENOSYS для любых новых.

Я думаю, что реализация не будет слишком сложной. Например, для amd64 «известные» системные вызовы могут быть выражены как n <= 181 || 186 <= n <= 235 || 237 <= n <= 334 || 424 <= n <= 439 . И такие выражения можно легко сгенерировать программно из таблиц системных вызовов.

94 тоже может быть связано.

Сегодня утром у меня недостаточно кофеина, но даст ли обработка ENOSYS возможность превратить большие списки разрешений в маленькие списки запрещенных для возможного выигрыша в производительности?

Я думаю, что реализация не будет слишком сложной. Например, для amd64 «известные» системные вызовы могут быть выражены как n <= 181 || 186 <= п <= 235 || 237 <= п <= 334 || 424 <= n <= 439. И такие выражения можно легко сгенерировать программно из таблиц системных вызовов.

Как вы намекаете, фактический BPF будет зависеть как от версии Arch/ABI, так и от версии ядра. В приведенном выше примере x86_64 BPF не будет таким уж плохим, но нам не повезет с другими арками/версиями. Несмотря на это, теперь это две проблемы, которые фактически запрашивают одно и то же, поэтому я думаю, что это то, что мы захотим сделать ... Я просто пока не собираюсь начинать прыгать вверх и вниз о том, насколько легко это будет; )

94 тоже может быть связано.

Типа да, типа нет. Это включает в себя диапазоны, но # 94 касается диапазонов аргументов, указанных вызывающей стороной (что, я думаю, мы все еще хотим сделать, PR просто появился в неудачное время, и я думаю, что API нуждается в некоторой настройке), тогда как то, о чем мы говорим, неявно созданные диапазоны системных вызовов, которые генерируются самой библиотекой.

Сегодня утром у меня недостаточно кофеина, но даст ли обработка ENOSYS возможность превратить большие списки разрешений в маленькие списки запрещенных для возможного выигрыша в производительности?

С точки зрения приложения, например, systemd, если вы пытаетесь заблокировать «новые» системные вызовы, то да… если мы говорим об одном и том же :)

Чтобы быть более конкретным... в настоящее время любой, кто пытается _безопасно_ заблокировать определенные системные вызовы, фактически должен внести в белый список, потому что вы не можете быть уверены, какие системные вызовы могут быть добавлены более новым ядром. Если мы можем запросить у libseccomp автоматическую блокировку неизвестных системных вызовов, значит, вместо этого мы можем безопасно переключиться на небольшой список запрещенных?

Чтобы быть более конкретным... в настоящее время любой, кто пытается _безопасно_ заблокировать определенные системные вызовы, фактически должен внести в белый список, потому что вы не можете быть уверены, какие системные вызовы могут быть добавлены более новым ядром. Если мы можем запросить у libseccomp автоматическую блокировку неизвестных системных вызовов, значит, вместо этого мы можем безопасно переключиться на небольшой список запрещенных?

Я искренне надеюсь, что мы сможем добиться этого, так как это было бы абсолютно потрясающей функцией. Например, Docker в настоящее время использует белый список, и их список по умолчанию теперь составляет около 240 системных вызовов (и постоянно растет).

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

Чтобы быть более конкретным... в настоящее время любой, кто пытается _безопасно_ заблокировать определенные системные вызовы, фактически должен внести в белый список, потому что вы не можете быть уверены, какие системные вызовы могут быть добавлены более новым ядром. Если мы можем запросить у libseccomp автоматическую блокировку неизвестных системных вызовов, значит, вместо этого мы можем безопасно переключиться на небольшой список запрещенных?

Я не понимаю, как это может работать. Неизвестный libseccomp и неизвестный автору списка запрещенных обычно означают разные вещи. Это означает, что концептуальная проблема не исчезнет, ​​даже если внутри libseccomp будет более четкое представление о поддерживаемых системных вызовах.

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

Я думаю, что реализация не будет слишком сложной. Например, для amd64 «известные» системные вызовы могут быть выражены как n <= 181 || 186 <= п <= 235 || 237 <= п <= 334 || 424 <= n <= 439. И такие выражения можно легко сгенерировать программно из таблиц системных вызовов.

Как вы намекаете, фактический BPF будет зависеть как от версии Arch/ABI, так и от версии ядра. В приведенном выше примере x86_64 BPF не будет таким уж плохим, но нам не повезет с другими арками/версиями. Несмотря на это, теперь это две проблемы, которые фактически запрашивают одно и то же, поэтому я думаю, что это то, что мы захотим сделать ... Я просто пока не собираюсь начинать прыгать вверх и вниз о том, насколько легко это будет; )

Таблицы довольно непрерывны:

>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-x86_64').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  5,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1, 90,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-alpha').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  3,  1,  2,  1,  1,
        1,  1,  1,  2,  1,  1,  1,  3, 12,  3,  3,  1, 11,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        2,  1,  1,  1,  1,  1,  1,  5,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1, 39,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  3,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        2,  1,  1,  1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-arm').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 2, 1, 2, 3, 4,
       1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 3, 1, 1,
       1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 3,
       1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 3, 3, 1, 1, 2, 1, 1, 1,
       1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
>>> l = {int(s[1]):s[0] for s in (s.split() for s in open('syscalls-riscv64').readlines()) if len(s)>1}; x = np.array(sorted(l.keys())); np.diff(x)
array([  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   2,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,  16,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1, 130,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1])

Я реализовал фильтр «известных» системных вызовов для systemd-nspawn в https://github.com/systemd/systemd/pull/16819. https://github.com/systemd/systemd/pull/16819/commits/158e30ffd9355a7640a7276276eb9219b6c87914 содержит дамп некоторых программ, сгенерированных libseccomp. Эти дампы длинные, поэтому я не буду их здесь повторять, но SCMP_FLTATR_CTL_OPTIMIZE делает программу более эффективной, но и более длинной. Вещи можно было сделать примерно в 50 раз короче, используя сравнения диапазонов.

Я только что нашел эту ветку, просто вмешался, чтобы сказать, что я думал в том же духе, и это определенно то, что Docker/runc также хотел бы решить. Выполнение этого с максимальной версией ядра, вероятно, является лучшим способом сделать это, потому что это означает, что авторам профилей (и средам выполнения контейнеров) не нужно отслеживать системные вызовы, добавленные не по порядку, или какой самый новый системный вызов был на момент написания. профиль.

Основываясь на приведенном выше обсуждении, похоже, что большинство (все?) людей здесь считают, что проблема № 11 - это правильный способ решить эту проблему; есть ли у кого-нибудь проблемы с закрытием этой проблемы в пользу переноса будущего обсуждения на исходную проблему (№ 11)?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги