Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Security
Code

Codificación Segura con Concurrencia en Swift 4

Spanish (Español) translation by Jean Perez (you can also view the original English article)

En mi artículo anterior sobre codificación segura en Swift, hablé sobre las vulnerabilidades de seguridad básica en Swift tales como ataques de inyección. Mientras que los ataques de inyección son comunes, hay otras formas de su aplicación puede ser comprometida. Una especie común pero a veces pasado por alto de vulnerabilidad es condiciones de carrera.

SWIFT 4 presenta Acceso Exclusivo a Memoria, que consiste en un conjunto de reglas para evitar que la misma área de memoria de acceso al mismo tiempo. Por ejemplo, el argumento de inout en Swift dice que un método que puede cambiar el valor del parámetro dentro del método.

Pero ¿qué pasa si pasamos en la misma variable para cambiar al mismo tiempo?

SWIFT 4 ha hecho mejoras que impiden a esta compilación. Pero mientras que Swift puede encontrar estos escenarios obvios en tiempo de compilación, es difícil, especialmente por razones de rendimiento, para encontrar problemas de acceso de memoria en código concurrente, y la mayoría de las vulnerabilidades de seguridad existe en forma de condiciones de carrera.

Condiciones de Carrera

Tan pronto como usted tiene más de un hilo que necesita escribir los mismos datos al mismo tiempo, puede ocurrir una condición de carrera. Condiciones de carrera causan corrupción de datos. Para estos tipos de ataques, las vulnerabilidades son generalmente más sutiles y las hazañas más creativas. Por ejemplo, podría ser la capacidad de alterar un recurso compartido para cambiar el flujo del código de seguridad en otro hilo, o en el caso de estado de la autenticación, un atacante podría ser capaz de aprovechar un espacio de tiempo entre el momento de la verificación y el tiempo de ustedes se de una bandera.

La manera de evitar condiciones de carrera es para sincronizar los datos. Sincronizar datos generalmente significa para que "encaje" para que sólo un subproceso puede acceder a esa parte del código a la vez (se dice que una exclusión mutua: para exclusión mutua). Mientras que usted puede hacer esto usando explícitamente la clase NSLock, hay potencial para perder lugares donde el código debe tener sido sincronizado. Realizar un seguimiento de las cerraduras y si ya está bloqueados o no, puede ser difíciles.

Grand Central Dispatch

En lugar de utilizar cerraduras primitivas, puede utilizar Grand Central Dispatch (GCD) — concurrencia modernos de Apple API diseñado para el funcionamiento y seguridad. No es necesario pensar en las trabas a ti mismo; hace el trabajo para usted detrás de las escenas.

Como se puede ver, es una API simple, use el MCD como su primera opción al diseñar su aplicación para la concurrencia.

Controles de seguridad de tiempo de ejecución de SWIFT no se puede realizar a través de hilos GCD porque crea un éxito considerable en el rendimiento. La solución es utilizar la herramienta del hilo de rosca desinfectante si se trabaja con varios subprocesos. La herramienta del hilo de rosca del desinfectante es muy bueno encontrar problemas que tal no vez nunca encuentre mirando el código usted mismo. Se puede activar yendo a Producto > Esquema > Editar Esquema > Diagnóstico y comprobación de la opción del Hilo de Rosca del Desinfectante.

Si el diseño de tu aplicación te hace trabajar con múltiples hilos, otra manera de protegerse de los problemas de seguridad de la concurrencia es tratar de diseñar tus clases para ser libre de bloqueo para que ningún código de sincronización es necesario en primer lugar. Esto requiere algunos real pensado en el diseño de su interfaz y puede incluso considerarse un arte separado en y de sí mismo!

El Inspector de Cubproceso Principal

Es importante mencionar que la corrupción de datos también puede ocurrir si haces actualizaciones de la interfaz de usuario en cualquier subproceso distinto del subproceso principal (cualquier otro hilo se conoce como un subproceso en segundo plano).

A veces no es incluso obvio que en un subproceso en segundo plano. Por ejemplo, delegateQueue de NSURLSession, cuando se ajusta a nil, será por defecto telefónicamente en un subproceso en segundo plano. Si puedes hacen actualizaciones de la interfaz de usuario o escriben sus datos en ese bloque, hay una buena oportunidad para las condiciones de carrera. (Arreglar esto envolviendo las actualizaciones de la interfaz de usuario en DispatchQueue.main.async{} o pasar en OperationQueue.main como la cola de delegado).

Nuevo en Xcode 9 y están habilitados por defecto es el corrector del hilo de rosca principal (Producto > Esquema > Editar Esquema > Diagnóstico > Control de API de tiempo de ejecución > Principal del Hilo de Checker). Si el código no está sincronizado, temas aparecerá en el Tiempo de Ejecución en el navegador del panel izquierdo de Xcode, así que preste atención a durante la prueba de su aplicación.

Para el código de seguridad, controladores de terminación que escribes ni las devoluciones de llamada deben documentarse si vuelven en el subproceso principal o no. Mejor aún, seguir más nuevo diseño en la API de Apple que permite pasar un completionQueue en el método para que puedas decidir claramente y ver qué hilo del bloque de terminación devuelve en.

Un Ejemplo del Mundo Real

¡Suficiente charla! Vamos a sumergirnos en un ejemplo.

Aquí no tenemos sincronización, pero más de un subproceso tiene acceso a los datos al mismo tiempo. Lo bueno de desinfectante del hilo de rosca es que detectará un caso como este. La manera moderna de GCD para solucionar este problema es asociar sus datos con una cola de envío de la serie.

Ahora el código está sincronizado con el bloque de async. . Quizás se pregunte cuándo elegir .async y cuándo utilizar .sync. Puede usar .async cuando su aplicación no necesita esperar hasta que se termina la operación dentro del bloque. Puede ser mejor explicado con un ejemplo.

En este ejemplo, el subproceso que hace la matriz de transacciones si contiene una determinada transacción proporciona, por lo que tiene que esperar. El otro hilo no toma ninguna acción después de anexar en la matriz de transacciones, por lo que no es necesario esperar hasta que termine el bloque.

Estos bloques de sincronización y async pueden ser envuelta en métodos que devuelven sus datos internos, como los métodos getter.

Dispersión de bloques GCD en todo las áreas de su código acceso a datos compartidos no están una buena práctica ya que es más difícil hacer un seguimiento de todos los lugares que necesitan estar sincronizados. Es mucho mejor tratar de mantener esta funcionalidad en un solo lugar.

Buen diseño métodos de descriptor de acceso es una forma de solucionar este problema. Utilizando métodos getter y setter y sólo utilizando estos métodos para acceder a los datos significa que usted puede sincronizar en un solo lugar. Esto evita tener que actualizar muchas partes de su código si usted está cambiando o la zona GCD de su código de refactorización.

Estructuras

Mientras solo propiedades almacenados pueden ser sincronizados a una clase, cambiar propiedades en una estructura realmente afecta a la estructura entera. SWIFT 4 ahora incluye protección para métodos que mutan las estructuras.

Primero veamos lo que parece una corrupción de la estructura (llamada una "raza de acceso rápido").

Los dos métodos en el ejemplo cambian las propiedades almacenadas, por lo que son marcados mutanting. Permite decir begin() de llamadas del subproceso 1 y 2 llamadas finish() del hilo de rosca. Aunque begin() sólo id y finish() sólo timestamp, es todavía una raza de acceso. Normalmente es mejor poner dentro de los métodos de descriptor de acceso, esto no se aplica a estructuras como la estructura entera tiene que ser exclusivo.

Una solución es cambiar la estructura de una clase al implementar el código concurrente. Si usted necesita la estructura por alguna razón, usted podría, en este ejemplo, crear una clase de bank que almacena estructuras de transaction. Luego se pueden sincronizar los llamadores de las estructuras dentro de la clase.

Aquí está un ejemplo:

Control de Acceso

Sería inútil tener esta protección cuando su interfaz expone un objeto mutante o un UnsafeMutablePointer a los datos compartidos, porque ahora cualquier usuario de la clase puede hacer lo que quieran con los datos sin la protección de GCD. Por el contrario, devolver las copias con los datos en el captador. Encapsulación de datos y diseño interfaz cuidadoso son importantes, sobre todo cuando el diseño de programas concurrentes, para asegurarse de los datos compartidos está realmente protegidos.

Asegúrese de que las variables sincronizadas están marcadas private, open o public, que permitirá a los miembros de cualquier archivo de origen para acceder a ella. Un cambio interesante en el Swift 4 es que se amplía el alcance del nivel de acceso privado para estar disponible en extensiones. Previamente sólo se podría utilizar dentro de la declaración de cierre, pero en 4 de Swift, una variable private puede consultarse en una extensión, como la extensión de la declaración es en el mismo archivo de fuente.

No sólo son variables de riesgo de corrupción de los datos sino también los archivos. Utilice la clase FileManager Foundation, que es seguro para subprocesos, y comprobar los indicadores de resultado de sus operaciones de archivo antes de continuar en el código.

Interfaz Con Objective-C

Muchos objetos de Objective-C tienen contrapartes mutable de su título. Versión mutable de NSString es llamado NSMutableString, NSArray es de NSMutableArray y así sucesivamente. Además del hecho de que estos objetos pueden ser transformados fuera de sincronización, los tipos de puntero procedentes de Objective-C también subversión Swift opcionales. Hay una buena probabilidad de que usted podría estar esperando un objeto en Swift, pero de Objective-C es devuelto como nula.

Si la aplicación se bloquea, da información valiosa sobre la lógica interna. En este caso, podría ser que introducidos por el usuario no fue debidamente comprobado y que área del flujo de la aplicación vale la pena mirar para tratar de explotar.

Aquí la solución es actualizar el código Objective-C para incluir anotaciones de aceptación de valores NULL. Podemos tomar una desviación ligera aquí como este Consejo se aplica a la interoperabilidad segura en general, ya sea entre Swift y Objective-C o entre dos otros lenguajes de programación.

Prólogo de sus variables de Objective-C con nullable cuando nil puede ser devuelto y nonnull cuando no debería.

También puede añadir nullable y nonnull a la lista de atributo de Objective-C propiedades.

La herramienta de Analizer estático en Xcode siempre ha sido genial para encontrar errores de Objective-C. Ahora con anotaciones de aceptación de valores nulos, en Xcode 9 usted puede utilizar el analizador estático de código Objective-C y encontrará las inconsistencias de la aceptación de valores nulos en el archivo. Para ello, navegando por Producto > Realizar Acción > Analizar.

Mientras está activado por defecto, también puede controlar los controles de aceptación de valores nulos en LLVM con - Wnullability* banderas.

Controles de aceptación de valores NULL son buenos para encontrar problemas en tiempo de compilación, pero no encuentran problemas de tiempo de ejecución. Por ejemplo, a veces asumimos en una parte de nuestro código que siempre existirá un valor opcional y uso la fuerza desenvolver ! en él. Se trata de una forma implícita sin envolver opcional, pero no hay ninguna garantía de que siempre va a existir. Después de todo, si fueron marcado opcional, es probable que nada en algún momento. Por lo tanto, es una buena idea para evitar abrir con fuerza !. En cambio, es una solución elegante comprobar en tiempo de ejecución así:

Para más ayuda, que hay una nueva característica añadida en Xcode 9 para realizar la aceptación de valores nulos comprueba en tiempo de ejecución. Es parte del desinfectante de comportamiento definido, y mientras que no está habilitada de forma predeterminada, puede habilitarlo dirigiéndose a Configuración Construir > Indefinido Desinfectante de Comportamiento y ajuste Si para Habilitar Controles de Anotación de Aceptación de Valores Nulos.

Lectura

Es una buena práctica escribir los métodos con sólo una entrada y una salida. No sólo es buena para facilitar la lectura, sino también para el avanzado soporte multithreading.

Digamos que una clase fue diseñada sin simultaneidad en mente. Más adelante los requisitos cambiaron para que ahora deben apoyar los métodos .lock() y .unlock() de NSLock. Cuando llegue el momento para colocar cerraduras alrededor de partes de su código, puede que necesite volver a escribir muchos de sus métodos para estar seguro. Es fácil perder un return ocultado en medio de un método que más tarde iba a bloquear la instancia de NSLock, que entonces puede causar una condición de carrera. También, como return desbloquea automáticamente el bloqueo. Otra parte de su código que asume que la cerradura se desbloquea y trata de bloquear nuevamente interbloqueo de la aplicación (la aplicación se congela y eventualmente ser terminada por el sistema). Accidentes también pueden ser vulnerabilidades de seguridad en código multiproceso si archivos de trabajo temporales nunca se limpien antes de que el subproceso termina. Si tu código tiene esta estructura:

Puede almacenar en su lugar el valor booleano, actualizar en el camino y volver al final del método. Luego el código de sincronización se puede envolver fácilmente en el método sin mucho trabajo.

El método .unlock() se debe llamar desde el mismo subproceso llama .lock(), si no resulta en comportamiento indefinido.

Prueba

A menudo, encontrar y solucionar vulnerabilidades en código concurrente desciende a la caza de errores. Cuando usted encuentra un error, es como sostener un espejo a ti mismo — una oportunidad de aprendizaje. Si usted olvidó sincronizar en un solo lugar, es probable que el mismo error en otros lugares en el código. Tomarse el tiempo para ver el resto de tu código para el mismo error cuando encuentre un error es una forma muy eficiente de prevenir vulnerabilidades de seguridad que serían mantener una y otra vez en el futuro la aplicación libera.

De hecho, muchas de las recientes fugas de iOS han sido a causa de repetidos errores de codificación encontradas IOKit de Apple. Una vez que sepa el estilo de desarrollo, puede comprobar otras partes del código de errores similares.

Error que encuentra es buena motivación para la reutilización de código. Sabiendo que usted ha arreglado un problema en un lugar y no tiene que ir a buscar las mismas ocurrencias en copiar/pegar código pueden ser un gran alivio.

Condiciones de carrera pueden ser complicadas encontrar durante la prueba ya que memoria puede dañarse sólo en el "camino correcto" a fin de ver el problema, y a veces los problemas aparecen mucho tiempo después en la ejecución de la aplicación.

Cuando está probando, cubrir todo el código. Ir a través de cada flujo y caso y probar al menos una vez cada línea de código. A veces ayuda a entrada de datos aleatorios (fuzzing las entradas), o elegir valores extremos con la esperanza de encontrar un caso de borde que no sería evidente mirando el código o utilizar la aplicación en forma normal. Esto, junto con las nuevas herramientas de Xcode está disponibles, puede ir una manera larga hacia la prevención de vulnerabilidades de seguridad. Mientras que el código no es 100% seguro, seguir una rutina, como primeros en pruebas funcionales, pruebas unitarias, prueba del sistema, pruebas de estrés y de regresión, será realmente pagar.

Más allá de la depuración de su aplicación, una cosa que es diferente para la configuración de lanzamiento (la configuración de aplicaciones publicadas en la tienda) es que las optimizaciones de código se incluyen. Por ejemplo, lo que el compilador piensa que es una operación puede conseguir optimizada hacia fuera, o una variable no puede quedarse más de lo necesario en un bloque simultáneo. Para su aplicación publicado, su código es realmente cambiado o diferente al que realizó la prueba. Esto significa que se pueden introducir errores que sólo existen una vez que liberas tu aplicación.

Si no está utilizando una configuración de prueba, asegúrese de que prueba su aplicación en modo de versión navegando al Producto > Esquema > Editar Esquema. Seleccione Ejecutar en la lista a la izquierda y en el panel de Información a la derecha, cambiar Configuración Construir para Liberar. Mientras que es bueno cubrir su aplicación toda en este modo, sabemos que debido a las optimizaciones, los puntos de desempate y el depurador no se comportan como se esperaba. Por ejemplo, descripciones variable no podrían estar disponibles a pesar de que el código se ejecuta correctamente.

Conclusión

En este post analizamos las condiciones de carrera y cómo evitarlos por codificación segura y usando herramientas como el sistema de desinfección del hilo de rosca. También hablamos de acceso exclusivo a memoria, que es una gran adición a Swift 4. Asegúrese de que está establecido en Completo Orden en Construir > Acceso Exclusivo a Memoria!

Recuerda que estas aplicaciones son sólo para el modo depurar, y si todavía está usando Swift 3.2, muchas de las aplicaciones discutieron vienen en forma de advertencias sólo. Así que tome las advertencias en serio, o mejor aún, hacen uso de todas las novedades disponibles adoptando Swift 4 hoy!

Y mientras estés aquí, algunos de mis otros posts de codificación segura para iOS y Swift!


Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.