Lectores como tú ayudan a apoyar a MUO. Cuando realiza una compra utilizando enlaces en nuestro sitio, podemos ganar una comisión de afiliado.

Una condición de carrera ocurre cuando dos operaciones deben ocurrir en un orden específico, pero pueden ejecutarse en el orden opuesto.

Por ejemplo, en una aplicación de subprocesos múltiples, dos subprocesos independientes pueden acceder a una variable común. Como resultado, si un subproceso cambia el valor de la variable, el otro aún puede usar la versión anterior, ignorando el valor más nuevo. Esto provocará resultados no deseados.

Para comprender mejor este modelo, sería bueno examinar de cerca el proceso de cambio de proceso del procesador.

Cómo un procesador cambia de proceso

Sistemas operativos modernos puede ejecutar más de un proceso simultáneamente, lo que se denomina multitarea. Cuando miras este proceso en términos de Ciclo de ejecución de la CPU, es posible que descubra que la multitarea realmente no existe.

En cambio, los procesadores cambian constantemente entre procesos para ejecutarlos simultáneamente o al menos actúan como si lo estuvieran haciendo. La CPU puede interrumpir un proceso antes de que se complete y reanudar un proceso diferente. El sistema operativo controla la gestión de estos procesos.

instagram viewer

Por ejemplo, el algoritmo Round Robin, uno de los algoritmos de conmutación más simples, funciona de la siguiente manera:

Generalmente, este algoritmo permite que cada proceso se ejecute durante períodos de tiempo muy pequeños, según lo determine el sistema operativo. Por ejemplo, esto podría ser un período de dos microsegundos.

La CPU toma cada proceso por turno y ejecuta comandos que durarán dos microsegundos. Luego continúa con el siguiente proceso, independientemente de si el actual ha terminado o no. Por lo tanto, desde el punto de vista de un usuario final, más de un proceso parece estar ejecutándose simultáneamente. Sin embargo, cuando miras detrás de escena, la CPU todavía está haciendo las cosas en orden.

Por cierto, como muestra el diagrama anterior, el algoritmo Round Robin carece de nociones de optimización o prioridad de procesamiento. Como resultado, es un método bastante rudimentario que rara vez se usa en sistemas reales.

Ahora, para comprender mejor todo esto, imagina que se están ejecutando dos subprocesos. Si los subprocesos acceden a una variable común, puede surgir una condición de carrera.

Ejemplo de aplicación web y condición de carrera

Consulte la sencilla aplicación Flask a continuación para reflexionar sobre un ejemplo concreto de todo lo que ha leído hasta ahora. El objetivo de esta aplicación es gestionar las transacciones de dinero que se van a realizar en la web. Guarde lo siguiente en un archivo llamado dinero.py:

de matraz importar Matraz
de matraz.ext.sqlalchemy importar SQLAlquimia

app = Frasco (__nombre__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (aplicación)

claseCuenta(db. Modelo):
id = db. columna (db. Entero, clave_principal = Verdadero)
cantidad = db. columna (db. Cadena(80), único = Verdadero)

definitivamente__en eso__(yo, cuenta):
self.cantidad = cantidad

definitivamente__repr__(ser):
devolver '' % propio.cantidad

@app.ruta("/")
definitivamenteHola():
cuenta = Cuenta.consulta.get(1) # Solo hay una billetera.
devolver "Dinero total = {}". formato (cuenta.cantidad)

@app.ruta("/enviar/")
definitivamenteenviar(cantidad):
cuenta = Cuenta.consulta.get(1)

si int (cuenta.cantidad) devolver "Saldo insuficiente. restablecer dinero con /reset!)"

cuenta.cantidad = int (cuenta.cantidad) - cantidad
db.sesión.commit()
devolver "Cantidad enviada = {}". formato (cantidad)

@app.ruta("/restablecer")
definitivamentereiniciar():
cuenta = Cuenta.consulta.get(1)
cuenta.cantidad = 5000
db.sesión.commit()
devolver "Restablecimiento de dinero".

si __nombre__ == "__principal__":
app.secret_key = '¡Hola, esta es una clave secreta!'
aplicación.ejecutar()

Para ejecutar este código, deberá crear un registro en la tabla de cuentas y continuar las transacciones sobre este registro. Como puede ver en el código, este es un entorno de prueba, por lo que realiza transacciones contra el primer registro de la tabla.

de dinero importar base de datos
base de datos.create_all()
de dinero importar Cuenta
cuenta = Cuenta (5000)
base de datos.sesión.agregar(cuenta)
base de datos.sesión.comprometerse()

Ahora ha creado una cuenta con un saldo de $ 5,000. Finalmente, ejecute el código fuente anterior con el siguiente comando, siempre que tenga instalados los paquetes Flask y Flask-SQLAlchemy:

pitóndinero.py

Entonces tiene la aplicación web Flask que realiza un proceso de extracción simple. Esta aplicación puede realizar las siguientes operaciones con enlaces de solicitud GET. Dado que Flask se ejecuta en el puerto 5000 de forma predeterminada, la dirección a la que accede es 127.0.0.1:5000/. La aplicación proporciona los siguientes puntos finales:

  • 127.0.0.1:5000/ muestra el saldo actual.
  • 127.0.0.1:5000/enviar/{cantidad} resta la cantidad de la cuenta.
  • 127.0.0.1:5000/restablecer restablece la cuenta a $ 5,000.

Ahora, en esta etapa, puede examinar cómo se produce la vulnerabilidad de la condición de carrera.

Probabilidad de una vulnerabilidad de condición de carrera

La aplicación web anterior contiene una posible vulnerabilidad de condición de carrera.

Imagine que tiene $ 5,000 para comenzar y cree dos solicitudes HTTP diferentes que enviarán $ 1. Para esto, puede enviar dos solicitudes HTTP diferentes al enlace 127.0.0.1:5000/enviar/1. Suponga que, tan pronto como el servidor web procesa la primera solicitud, la CPU detiene este proceso y procesa la segunda solicitud. Por ejemplo, el primer proceso puede detenerse después de ejecutar la siguiente línea de código:

cuenta.cantidad = En t(cuenta.cantidad) - cantidad

Este código ha calculado un nuevo total pero aún no ha guardado el registro en la base de datos. Cuando comience la segunda solicitud, realizará el mismo cálculo, restando $1 del valor en la base de datos ($5,000) y almacenando el resultado. Cuando se reanude el primer proceso, almacenará su propio valor, $4999, que no reflejará el saldo de cuenta más reciente.

Entonces, se completaron dos solicitudes y cada una debería haber restado $1 del saldo de la cuenta, lo que resultó en un nuevo saldo de $4,998. Pero, dependiendo del orden en que el servidor web los procese, el saldo final de la cuenta puede ser de $4,999.

Imagine que envía 128 solicitudes para realizar una transferencia de $1 al sistema de destino en un período de tiempo de cinco segundos. Como resultado de esta transacción, el estado de cuenta esperado será de $5,000 - $128 = $4,875. Sin embargo, debido a las condiciones de la carrera, el saldo final puede variar entre $4875 y $4999.

Los programadores son uno de los componentes más importantes de la seguridad

En un proyecto de software, como programador, tienes bastantes responsabilidades. El ejemplo anterior fue para una aplicación simple de transferencia de dinero. Imagine trabajar en un proyecto de software que administra una cuenta bancaria o el backend de un gran sitio de comercio electrónico.

Debe estar familiarizado con tales vulnerabilidades para que el programa que ha escrito para protegerlas esté libre de vulnerabilidades. Esto requiere una fuerte responsabilidad.

Una vulnerabilidad de condición de carrera es solo una de ellas. Independientemente de la tecnología que utilice, debe estar atento a las vulnerabilidades en el código que escribe. Una de las habilidades más importantes que puede adquirir como programador es la familiaridad con la seguridad del software.