Что такое уязвимость состояния гонки?
Программирование

Что такое уязвимость состояния гонки?

Разобраться в условиях гонки может быть непросто, но важно понимать их последствия

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

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

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

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

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

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

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

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

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

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

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

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

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

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class Account(db.Model):
id = db.Column(db.Integer, primary_key = True)
amount = db.Column(db.String(80), unique = True)

def __init__(self, count):
self.amount = amount

def __repr__(self):
return '' % self.amount

@app.route('/')
def hi():
account = Account.query.get(1) # There is only one wallet.
return 'Total Money = {}'format(account.amount)

@app.route('/send/')
def send(amount):
account = Account.query.get(1)

if int(account.amount) < amount:
return 'Insufficient balance. Reset money with /reset!)'

account.amount = int(account.amount) - amount
db.session.commit()
return 'Amount sent = {}'format(amount)

@app.route('/reset')
def reset():
account = Account.query.get(1)
account.amount = 5000
db.session.commit()
return 'Money reset.'

if __name__ == '__main__':
app.secret_key = 'heLLoTHisIsSeCReTKey!'
app.run()

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

from money import db
db.create_all()
from money import Account
account = Account(5000)
db.session.add(account)
db.session.commit()

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

python money.py

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

  • 127. 0. 0. 1:5000/ отображает текущий баланс.
  • 127. 0. 0. 1:5000/send/{amount} вычитает сумму со счета.
  • 127. 0. 0. 1:5000/reset сбрасывает счет до $5,000.
  • Теперь, на этом этапе, вы можете изучить, как возникает уязвимость состояния гонки

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

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

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

    account.amount = int(account.amount) - amount

    Этот код рассчитал новый итог, но еще не сохранил запись в базе данных. Когда начнется второй запрос, он выполнит тот же расчет, вычтя $1 из значения в базе данных —$5,000— и сохранив результат. Когда первый процесс возобновится, он сохранит собственное значение —$4,999— которое не будет отражать последний баланс счета

    Итак, два запроса выполнены, и каждый из них должен был вычесть $1 из баланса счета, в результате чего новый баланс составил $4,998. Но в зависимости от того, в каком порядке их обрабатывает веб-сервер, конечный баланс счета может быть равен $4 999

    Представьте, что вы отправляете 128 запросов на перевод $1 в целевую систему в течение пяти секунд. В результате этой операции ожидаемая выписка по счету составит $5,000 – $128 = $4,875. Однако из-за условия гонки конечный баланс может варьироваться между $4,875 и $4,999

    Программисты – одна из важнейших составляющих безопасности

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

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

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

    Об авторе

    Алексей Белоусов

    Привет, меня зовут Филипп. Я фрилансер энтузиаст . В свободное время занимаюсь переводом статей и пишу о потребительских технологиях для широкого круга изданий , не переставая питать большую страсть ко всему мобильному =)

    Комментировать

    Оставить комментарий