Такие читатели, как вы, помогают поддерживать MUO. Когда вы совершаете покупку по ссылкам на нашем сайте, мы можем получать партнерскую комиссию.

Состояние гонки возникает, когда две операции должны выполняться в определенном порядке, но они могут выполняться в противоположном порядке.

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

Чтобы лучше понять эту модель, было бы хорошо внимательно изучить процесс переключения процессора.

Как процессор переключает процессы

Современные операционные системы может запускать более одного процесса одновременно, что называется многозадачностью. Если посмотреть на этот процесс с точки зрения Цикл выполнения процессора, вы можете обнаружить, что многозадачность на самом деле не существует.

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

Например, алгоритм Round Robin, один из простейших алгоритмов переключения, работает следующим образом:

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

ЦП берет каждый процесс по очереди и выполняет команды, которые будут выполняться в течение двух микросекунд. Затем он переходит к следующему процессу, независимо от того, завершился текущий или нет. Таким образом, с точки зрения конечного пользователя одновременно работает более одного процесса. Однако, если заглянуть за кулисы, процессор по-прежнему делает все по порядку.

Кстати, как видно из диаграммы выше, в алгоритме Round Robin отсутствуют какие-либо понятия оптимизации или приоритета обработки. В результате это довольно рудиментарный метод, который редко используется в реальных системах.

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

Пример веб-приложения и состояние гонки

Ознакомьтесь с простым приложением Flask ниже, чтобы поразмыслить на конкретном примере всего, что вы прочитали до сих пор. Целью этого приложения является управление денежными транзакциями, которые будут происходить в Интернете. Сохраните следующее в файле с именем деньги.ру:

от колба Импортировать Колба
от flask.ext.sqlalchemy Импортировать SQLAlchemy

приложение = Фляга (__имя__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (приложение)

сортСчет(дб. Модель):
идентификатор = БД. Колонка (дб. Целое число, первичный_ключ = Истинный)
сумма = дб. Колонка (дб. Нить(80), уникальный = Истинный)

деф__в этом__(себя, считать):
самостоятельная сумма = сумма

деф__repr__(себя):
возвращаться '' % само.сумма

@приложение.маршрут("/")
дефпривет():
учетная запись = учетная запись.запрос.получить(1) # Есть только один кошелек.
возвращаться «Всего в деньгах = {}».format (account.amount)

@app.route("/отправить/")
дефотправлять(количество):
учетная запись = учетная запись.запрос.получить(1)

если int (счет.сумма) < сумма:
возвращаться "Недостаточный баланс. Сбросить деньги с /reset!)"

account.amount = int (account.amount) - сумма
db.session.commit()
возвращаться "Отправленная сумма = {}".format (сумма)

@app.route("/сброс")
дефперезагрузить():
учетная запись = учетная запись.запрос.получить(1)
счет.сумма = 5000
db.session.commit()
возвращаться «Сброс денег».

если __name__ == "__main__":
app.secret_key = 'HELLoTHisIsSeCReTKey!'
app.run()

Чтобы запустить этот код, вам нужно создать запись в таблице учетных записей и продолжить транзакции по этой записи. Как видно из кода, это тестовая среда, поэтому она выполняет транзакции для первой записи в таблице.

от деньги Импортировать дБ
дБ.create_all()
от деньги Импортировать Счет
счет = счет (5000)
дБ.сессия.добавлять(счет)
дБ.сессия.совершить()

Вы создали учетную запись с балансом в 5000 долларов. Наконец, запустите приведенный выше исходный код с помощью следующей команды, если у вас установлены пакеты Flask и Flask-SQLAlchemy:

питонденьги.py

Итак, у вас есть веб-приложение Flask, которое выполняет простой процесс извлечения. Это приложение может выполнять следующие операции со ссылками запроса GET. Поскольку Flask по умолчанию работает на порту 5000, адрес, по которому вы получаете к нему доступ, 127.0.0.1:5000/. Приложение предоставляет следующие конечные точки:

  • 127.0.0.1:5000/ отображает текущий баланс.
  • 127.0.0.1:5000/отправить/{сумма} списывает сумму со счета.
  • 127.0.0.1:5000/сброс сбрасывает счет до 5000 долларов.

Теперь, на этом этапе, вы можете изучить, как возникает уязвимость состояния гонки.

Вероятность уязвимости состояния гонки

Приведенное выше веб-приложение содержит возможную уязвимость состояния гонки.

Представьте, что у вас есть 5000 долларов для начала, и вы создаете два разных HTTP-запроса, которые отправят 1 доллар. Для этого можно отправить два разных HTTP-запроса на ссылку 127.0.0.1:5000/отправить/1. Предположим, что как только веб-сервер обрабатывает первый запрос, ЦП останавливает этот процесс и обрабатывает второй запрос. Например, первый процесс может остановиться после выполнения следующей строки кода:

счет.сумма = инт(account.amount) - сумма

Этот код вычислил новую сумму, но еще не сохранил запись в базе данных. Когда начнется второй запрос, он выполнит тот же расчет, вычтя 1 доллар из значения в базе данных (5000 долларов) и сохранив результат. Когда первый процесс возобновится, он сохранит свое собственное значение — 4999 долларов США, которое не будет отражать самый последний баланс счета.

Таким образом, два запроса были выполнены, и каждый из них должен был вычесть 1 доллар США из баланса счета, в результате чего новый баланс составил 4998 долларов США. Но, в зависимости от порядка их обработки веб-сервером, окончательный баланс счета может составлять 4999 долларов.

Представьте, что вы отправляете 128 запросов на перевод 1 доллара в целевую систему за пять секунд. В результате этой транзакции ожидаемая выписка по счету составит 5000 долларов – 128 долларов США = 4 875 долларов США. Однако из-за условий гонки окончательный баланс может варьироваться от 4875 до 4999 долларов.

Программисты — один из важнейших компонентов безопасности

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

Вы должны быть знакомы с такими уязвимостями, чтобы написанная вами программа для их защиты была свободна от уязвимостей. Это требует сильной ответственности.

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