Topic: INSTEAD 2.0.0 (2.0.1)

Накануне 5-летнего юбилея проекта с радостью сообщаю о выпуске INSTEAD 2.0.0.

Список изменений:

* Теперь INSTEAD распространяется под MIT лицензией, которая не запрещает использование кода в закрытых проектах;
* Код теперь расположен на http://github.com/instead-hub/instead
* Режим песочницы (игра не может повредить данные, за пределами своего каталога);
* Заголовок окна теперь содержит название игры;
* Возможность запускать игру просто указав путь к ней: sdl-instead <path>;
* Исправления в системе сборки;
* Поддержка cmake;
* Добавления в doc/examples;
* Параметры -lua and -luac (для проверки синтаксиса кода игры);
* Теперь консоль -debug в Windows при аварийном выходе не закрывается;
* game.gui.hidetitle;
* stead.api_atleast() and stead.atleast();
* stead.tonum, stead.tostr, stead.type, stead.ipairs, stead.pairs, stead.opairs;
* Исправление в сохранении при не ASCII путях и не UTF-8 кодировке игры;
* Исправление ошибки при изменении языка интерфейса;
* Исправление ошибки поворота и масштабирования анимированных gif;
* Исправление ошибки отображения анимированных gif и курсора мыши;
* Исправление других ошибок и исправления lua части;
* Оформление кода;

Спасибо всех, кто принял участие в выпуске и просто любителей и писателей текстовых приключений.

Бинарные сборки будут обновляться по мере их готовности.

Re: INSTEAD 2.0.0 (2.0.1)

Так держать! Моё сердце радуется, когда я вижу что Instead продолжает жить и развиваться !  big_smile

Re: INSTEAD 2.0.0 (2.0.1)

Пришлось выпустить срочное исправление 2.0.1. Не затрагивает бинарные сборки.

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

* Режим песочницы (игра не может повредить данные, за пределами своего каталога);

Ну и песочница... Её же обойти - как 2 пальца об асфальт.

Пример:
io.open('../../stead/stead.lua', 'a'):write('os.execute("xmessage \'external command executed!\'")')

Другой пример:
package.loadlib('/usr/lib/liblua.so', 'luaopen_os')().execute("xmessage 'external command executed!'")

Ещё пример:
select(2, debug.getupvalue(io.open, 1))(os.getenv('HOME') .. '/some.file.in.home.dir', 'w'):write('Overwriting external file!')

Re: INSTEAD 2.0.0 (2.0.1)

да  - append mode я пропустил

и за остальное спасибо. я конечно не достаточно серъезно подоше к вопросу - в след версиях буду улучшать.

есть идеи еще? сделать просто по другому в текущей архитектуре я не вижу пути. Только блокируя функции в stead.lua

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

да  - append mode я пропустил

и за остальное спасибо. я конечно не достаточно серъезно подоше к вопросу - в след версиях буду улучшать.

есть идеи еще? сделать просто по другому в текущей архитектуре я не вижу пути. Только блокируя функции в stead.lua

Точечный отстрел по одной функции ни к чему не приведёт. В Lua больше половины функций опасны, в том числе активно используемые в играх. require() может загрузить какую-нибудь установленную в системе библиотеку для Lua, вроде LuaPOSIX; dofile (и ваша doencfile) может загружать не только исходники, но и байткод, от повреждения памяти которым Lua5.1 защищается неэффективно, а Lua5.2 даже не пытается защищаться; и так далее. Я попробую придумать, что можно сделать в рамках вашей архитектуры, но ничего не обещаю. Пока считайте, что у вас нет песочницы.

Re: INSTEAD 2.0.0 (2.0.1)

В целом, я согласен.
Но по-моему насчет половины это все-таки перегиб. smile и точечные выстрелы вполне работают.
Debug = nil. Load lib. Append open.
И по моему основная проблема остается  с require.
Про байткод и dofile не знал, но мне кажется этим уже можно пренебречь, учитывая область применения.

Re: INSTEAD 2.0.0 (2.0.1)

По сути, песочница сделанная по принципу setfenv все равно не дала бы то, что надо. Так как io.open нужен. dofile нужен. require нужен. dofile еще можно сделать своим, но require -- не хотелось бы...

Запись в память при специально сформированно байткоде - это все же для данного случая не настолько существенно. Если атакующий квалифицирован настолько, что готов заниматься подобным (да еще и с учетом конкретной среды жертвы), то для этого ему INSTEAD не нужен. Можно проще способы найти. Но можно ограничить выполнение только текстом. То есть тут проблемы вроде нет.

А вот с require да. Я думал об этом, но уперся в то, что по идее атакующий может сам формировать пути, по которому ищутся модули. И пока тоже оставил.

Но кроме этих вещей, разве еще что то осталось?

Пока закоммитил то, что выше. Жду идей. smile

http://lua-users.org/wiki/SandBoxes -- список безопасных и  небезопасных функций. Те, что опасны в списке -- не всегда опасны у нас, так как у нас не считается нарушением нарушение работы lua машины вне песочницы (она единственная и ресетится при запуске игры).

Ну кстати, можно же и require переопределить. Забивать всегда cpath. package.preload. path...

local build_sandbox_require = function(path)
    return stead.hook(require, function(f, name, ...)
        package.path = path
        package.cpath = {}
        package.preload = {}
        return f(name, ...)
    end)
end
require = build_sandbox_require(package.path)

И вроде бы все? Остается dofile.. В 5.2 решается mode. В 5.1 можно забить. Что еще я не учел? Кажется, остальное нас не затрагивает. Так что драмы особой кажется нет.

9 (edited by toneymoon 2014-01-28 11:20:57)

Re: INSTEAD 2.0.0 (2.0.1)

Instead 2.0.0 (Arch GNU/Linux):
При попытке запуска "Последнего рейса" получаю ошибку

Error: /usr/share/instead/stead//stead.lua:3159: attempt to call field 'gfind' (a nil value)
stack traceback:
    /usr/share/instead/stead//stead.lua:3159: in function 'instead_version'
    /usr/share/instead/stead//stead.lua:3445: in main chunk
Я пытался грызть стол... Глупый стол! Почти сломать мне зуба!

Re: INSTEAD 2.0.0 (2.0.1)

2.0.1 решает проблему. автору мантейнера в арче отписал,

11 (edited by /dev/random 2014-01-28 11:39:02)

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:
local build_sandbox_require = function(path)
    return stead.hook(require, function(f, name, ...)
        package.path = path
        package.cpath = {}
        package.preload = {}
        return f(name, ...)
    end)
end
require = build_sandbox_require(package.path)

require() - это просто обёртка вокруг package.loaders (package.searchers в 5.2). Пользователь их и вручную вызвать может. Это раз. require() тоже может загружать байткод. Это два. require() пользуется собственной, локальной ссылкой на package, а вы модифицируете глобальную, которая может быть подменена пользователем. Это три. По-хорошему, вообще стоит сделать package=nil.

А насчёт io.open() - опять не так. Теперь он проверяет только a и w, не проверяя, к примеру, r+ и wb. Безопасны только r и rb, всё остальное опасно.

Re: INSTEAD 2.0.0 (2.0.1)

wb проверяет по идее - так как w в строке находится. (А - я еще забыл [], да) Про r+ снова я проглядел. Спасибо. smile
package.loaders (package.searchers) -- буду изучать.

package - я вроде думал, что меняя package.path мы управляем тем, откуда require будет брать модули? Собственно, я перебиваю это.

А. Понял - package может быть целиком подменен. Ок - сейчас попробую исправить

Если я сделаю package = nil тогда наверное require перестанет искать в путях instead же?

Re: INSTEAD 2.0.0 (2.0.1)

Так. Правильно ли я понимаю, что loaders все равно анализируют path/cpath/preload?

Re: INSTEAD 2.0.0 (2.0.1)

Если я сделаю package = nil тогда наверное require перестанет искать в путях instead же?

Нет. Я уже написал, что require использует локальную ссылку на эту таблицу.

Так. Правильно ли я понимаю, что loaders все равно анализируют path/cpath/preload?

Да. И если пользователь их вызовет вручную, то обойдёт вашу обёртку вокруг require. И это далеко не единственная опасная вещь в package.

Re: INSTEAD 2.0.0 (2.0.1)

Про loaders  я понял. Вроде скоро закоммичу. Но мне неясно про локальную ссылку. Когда она создается?
Хотя - если все так - то действительно package = nil лучше всего.
Я тут пока сделал перехват всех loaders  -- похоже это лишнее.

--------

Хорошо. Пушнул изменения,.теперь остался только байткод или я снова где то косячу? smile

Хотя нет. Все таки лучше перехватывать loaders. Чтобы заменить пути поиска. Вернее просто забить пути перед = nil.

пушнул изменения - так норм?

16 (edited by /dev/random 2014-01-28 13:19:49)

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

Но мне неясно про локальную ссылку. Когда она создается?

Сразу после создания этой таблицы, ещё до присваивания её глобальной переменной packages.

пушнул изменения - так норм?

С require и io.open - вроде бы, всё в порядке. Но я бы всё-таки составил белый список безопасных (или обработанных, как require) функций, после чего прошёлся бы pairs()'ом по _G, io и прочему и вырезал бы всё, что в список не попало. А то так можно вечно всё новые и новые дыры находить. Сейчас, например, внезапно вспомнил, что io.output() тоже умеет открывать файлы.

Re: INSTEAD 2.0.0 (2.0.1)

Да, про io.output я тоже видел. Забыл. smile
Сделав белый список - я боюсь что то выкинуть из того, что используется в какой-то игре. Но вообще подумаю. Спасибо за помощь!
---------
Прошелся по всем функциям pairs. Отметил опасные / не нужные с тз INSTEAD:


_G.gcinfo
os.exit
os.setlocale
os.date
os.getenv
os.difftime
os.remove  (*)
os.time
os.execute (*)
os.clock
os.tmpname (*)
os.rename (*)
_G.getfenv
_G.pairs
_G.assert
_G.tonumber
io.lines
io.write
io.close
io.flush
io.open (*)
io.output (*)
io.type
io.read
io.input
io.popen (*)
io.tmpfile (пока не убрана) вроде не опасна - но и не нужна
_G.load
_G.doencfile
_G.module
coroutine.resume
coroutine.yield
coroutine.running
coroutine.status
coroutine.wrap
coroutine.create
_G.loadstring
string.sub
string.upper
string.len
string.gfind
string.rep
string.find
string.match
string.char
string.dump
string.gmatch
string.reverse
string.byte
string.format
string.gsub
string.lower
_G.xpcall
package.loadlib (* убран)
debug (* убрали)
table.setn
table.insert
table.getn
table.foreachi
table.maxn
table.foreach
table.concat
table.remove
table.sort
math.log
math.max
math.acos
math.ldexp
math.cos
math.tanh
math.pow
math.deg
math.tan
math.cosh
math.sinh
math.random
math.randomseed
math.frexp
math.ceil
math.floor
math.rad
math.abs
math.sqrt
math.modf
math.asin
math.min
math.mod
math.fmod
math.log10
math.atan2
math.exp
math.sin
package * - убран
_G.require (*) - защищен убиванием package
_G.setmetatable
_G.next
_G.ipairs
_G.rawequal
_G.collectgarbage
_G.newproxy
_G.table_get_maxn
_G.rawset
_G.getmetatable
_G.print
_G.pcall
_G.tostring
_G.type
_G.unpack
_G.select
_G.rawget
_G.setfenv
_G.dofile
_G.error
_G.loadfile

Вроде бы все, что было можно -- сделано. Остается атака на выход за границы буфера. Но, думаю, пренебречь этим можно.
P.S> что такое newproxy пока не знаю smile но думаю к фс отношения не имеет
------
Кстати, есть же LUA_SIGNATURE.. Можно запретить и байткод до кучи...
------
В принципе я сделал патч на запрет байткода. (no-precompiled branch) Но думается мне, что в этом есть немного вреда. Есть мысли? Оставляем или нет?

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

В принципе я сделал патч на запрет байткода. (no-precompiled branch) Но думается мне, что в этом есть немного вреда. Есть мысли? Оставляем или нет?

Вреда в нём нет, поскольку байткод в Lua специфичен для платформы и версии интерпретатора. У человека, способного осилить написание игры для INSTEAD, несомненно, IQ не настолько низок, чтобы распространять эту игру в байткоде, формат которого изменится при обновлении интерпретатора.

Вот только этого изменения мало. Есть ещё load/loadstring/loadfile. Есть строчка "./?.lua" в package.path, которая позволяет require читать файлы, в том числе с байткодом, из текущей директории. И если эту строчку оттуда убрать, то вот это уже может причинить играм вред. Я не удивлюсь, если многие из них загружают локальные скрипты через require.

Я вижу только 2 способа заблокировать байткод, не ломая игры. Первый: реализовать все эти функции самостоятельно. Это может оказаться довольно трудозатратно. И второй: поставлять в комплекте патченный интерпретатор Lua, который вообще не умеет загружать байткод. Патч тривиален, но пользователь потеряет возможность выбирать интерпретатор на своё усмотрение.

Re: INSTEAD 2.0.0 (2.0.1)

Да. Тоже понял это, пока ехал домой. И если loadstring еще можно как то запинать на луа, то require требует более глобальных изменений.

20 (edited by /dev/random 2014-01-28 18:35:50)

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

P.S> что такое newproxy пока не знаю smile но думаю к фс отношения не имеет

newproxy - это недокументированная функция. Это одна из главных причин, почему я считаю, что нужен белый список, а не чёрный. Если есть одна недокументированная функция, то при очередном обновлении может внезапно появиться другая. Лучше, если эта новая функция будет автоматически удалена из песочницы как неизвестная.

Что newproxy делает:
* при вызове с аргументом false, создаёт и возвращает уникальный, но ничего не делающий userdata без метатаблицы.
* при вызове с аргументом true, создаёт и возвращает userdata с пустой метатаблицей. Её можно получить с помощью getmetatable и заполнить.
* при передаче в качестве аргумента userdata, ранее возвращённого этой функцией, создаёт и возвращает новый userdata с той же метатаблицей, что и у переданного аргумента.
* другие аргументы запрещены.

В целом, эта функция безопасна, если вы сами её нигде не используете.

Re: INSTEAD 2.0.0 (2.0.1)

Проблема в том, что песочница сделана не через окружение. И пример с package показывает, что и глобальные таблицы или переменные могут дать лазейку. Так что по моему обход G принципиально ситуацию не улучшит. В идеале нужно постепенно двигаться в сторону окружения. Переход прямо сейчас не получится, но можно попробовать начать его делать. Тем более вызов из c кода относительно централизован.

Re: INSTEAD 2.0.0 (2.0.1)

Peter wrote:

Проблема в том, что песочница сделана не через окружение. И пример с package показывает, что и глобальные таблицы или переменные могут дать лазейку. Так что по моему обход G принципиально ситуацию не улучшит. В идеале нужно постепенно двигаться в сторону окружения. Переход прямо сейчас не получится, но можно попробовать начать его делать. Тем более вызов из c кода относительно централизован.

ИМХО, лучше будет даже не окружение, а полная изоляция всего Lua-контекста, через C. Но это, конечно, долго.

Re: INSTEAD 2.0.0 (2.0.1)

В общем в 2.0.2 пока революций не будет, но стало гораздо лучше все-равно.

Re: INSTEAD 2.0.0 (2.0.1)

Внезапно пришло в голову: симлинк. Игровая директория может содержать симлинк наружу.

Re: INSTEAD 2.0.0 (2.0.1)

я не уверен, что zip поддерживает симлинки. Репозитории отдают zip. 
Но да, надо бы подумать. Хотя, если так посмотреть, и распаковка архива может заменить какие-то файлы юзера. Особенно, если абсолютные пути. Хотя в случае с инстедом можно кинуть линку на каталог типа  ../ - и открыть к нему доступ, это злее.