Mapa de la lección
Lección 08 publicada. Mapa, ejemplo, EX015.
flowchart TD
N0["Resultados de aprendizaje y conexión"]
N1["8.1 La entrada es una frontera de seguridad"]
N2["8.2 Reglas de validación"]
N3["8.4 Pruebas"]
N4["8.6 Actividad práctica"]
N5["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX015"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje y conexión"]
N1["8.1 La entrada es una frontera de seguridad"]
N2["8.2 Reglas de validación"]
N3["8.4 Pruebas"]
N4["8.6 Actividad práctica"]
N5["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX015"]
[Resultados de aprendizaje y conexión]
|
v
[8.1 La entrada es una frontera de seguridad]
|
v
[8.2 Reglas de validación]
|
v
[8.4 Pruebas]
|
v
[8.6 Actividad práctica]
|
v
[Síntesis del capítulo]
|
v
[Ejemplo resuelto] --> [Ejercicio EX015]
Resultados de aprendizaje y conexión
Después de este capítulo usted podrá (1) validar entradas no confiables con reglas explícitas y rechazar lo que no se ajuste a ellas; (2) gestionar los errores para que un programa falle de forma segura y visible; (3) escribir pruebas unitarias, incluidas pruebas de casos límite y mal formados; y (4) explicar por qué el análisis sintáctico de la entrada es una frontera de seguridad. Se apoya en los capítulos 3 y 7 y prepara el capítulo 9 (datos) y el capítulo 27 (verificación).
8.1 La entrada es una frontera de seguridad
Todo programa que lee datos del exterior (un archivo, un mensaje de red, lo que introduce un operador) hace suposiciones sobre ellos. Cuando las suposiciones son erróneas, los programas se bloquean, calculan mal o, peor aún, hacen algo que eligió un atacante. Los catálogos de debilidades del software están dominados por errores en el tratamiento de la entrada: inyección, lecturas y escrituras fuera de límites, validación incorrecta de la entrada (CWE Top 25). El patrón defensivo es analizar, no adivinar: definir con exactitud cómo es una entrada válida, convertir el texto bruto en valores tipados en un único lugar y rechazar todo lo demás con un error claro.
8.2 Reglas de validación
Para un punto de consigna enviado a una estación de bombeo, una entrada válida podría ser: un número; finito (no «NaN» ni infinito); dentro de los límites de ingeniería (0,5–4,5 m); que no cambie más de 0,5 m respecto del valor actual; procedente de una fuente autorizada. Cada regla debe ser explícita, estar probada y registrarse cuando rechaza algo. La lista de permitidos (aceptar solo las formas conocidas como buenas) es más sólida que la lista de denegados (rechazar las formas conocidas como malas), porque los atacantes encuentran formas en las que usted no pensó. En Python, float("nan") funciona, y tanto nan < 0.5 como nan > 4.5 son falsos, de modo que una comprobación de rango escrita como «rechazar si está por debajo o por encima» acepta NaN sin hacer ruido. La comprobación robusta es «aceptar solo si math.isfinite(x) y 0.5 <= x <= 4.5».
8.3 Errores y fallar de forma segura
Cuando la validación falla, el programa debe hacer algo definido: rechazar la orden y conservar el último valor bueno, generar una alarma, registrar el motivo. No debe continuar con un valor predeterminado que resulte peligroso (un punto de consigna de cero) ni tragarse el error en silencio (except: pass). En contextos de control, «seguro» es una decisión del proceso (una válvula puede necesitar fallar cerrada para el cloro pero abierta para el agua de refrigeración), por lo que el programador implementa un estado de falla que ha especificado el responsable del proceso, en lugar de inventarlo.
8.4 Pruebas
Una prueba unitaria comprueba una función con casos conocidos. Los buenos conjuntos de pruebas incluyen valores típicos, límites (exactamente 0,5 y 4,5), valores justo fuera de los límites (0,49, 4,51), entradas mal formadas (cadena vacía, "abc", "1e309", "nan") y casos dependientes del estado (un valor válido que cambia demasiado rápido). El módulo unittest incorporado en Python o simples sentencias assert bastan en este nivel. Una prueba que nunca ha fallado nunca ha demostrado que funcione: ejecute cada prueba nueva una vez contra una versión estropeada a propósito.
Para el validador de puntos de consigna de CW, la versión contra la que fallar es la v1: su prueba de "nan" devuelve True y su prueba de "abc" lanza una excepción, de modo que dos de las siete pruebas fallan antes de la corrección y ninguna después.
8.5 Ejemplo resuelto: validador de puntos de consigna de CW (sintético)
Este caso es sintético. La interfaz de operador de Calder Water envía como texto los puntos de consigna del nivel de los tanques. Versión 1 del validador:
def accept_v1(text, current):
x = float(text)
if x < 0.5 or x > 4.5: return False
if abs(x - current) > 0.5: return False
return True
accept_v1("nan", 3.0) devuelve True: ambas comparaciones con NaN son falsas, y abs(nan - 3.0) > 0.5 también es falsa. accept_v1("abc", 3.0) lanza una excepción que, según quién llame, puede bloquear la interfaz. Versión 2:
import math
def accept_v2(text, current):
try:
x = float(text)
except ValueError:
return False
return math.isfinite(x) and 0.5 <= x <= 4.5 and abs(x - current) <= 0.5
La versión 2 dice qué es aceptable en lugar de qué no lo es. Pruebas: ("3.2", 3.0) → True; ("4.5", 4.2) → True; ("4.51", 4.2) → False; ("nan", 3.0) → False; ("inf", 3.0) → False; ("abc", 3.0) → False; ("2.0", 3.0) → False (paso demasiado grande).
El bloque try convierte un bloqueo en un rechazo, y math.isfinite elimina NaN e infinito antes de cualquier comparación. Lo que la v2 todavía no hace es decir por qué rechazó: la interfaz de operador debería registrar qué regla falló.
8.6 Actividad práctica
Implemente ambas versiones y las siete pruebas anteriores. Ejecute las pruebas contra la v1 y anote cuáles fallan; después, contra la v2. Añada dos pruebas propias que crea que la v2 todavía podría resolver mal (pista: espacios en blanco, cadenas muy largas, una coma como separador decimal) y decida si el comportamiento es aceptable. Contra la v1, espere que la prueba de "nan" falle (devuelve True) y que "abc" lance una excepción en lugar de devolver False. Contra la v2 deberían pasar las siete. En sus propias pruebas, float de Python acepta " 3.2 " con espacios, lo que probablemente está bien; "3,2" se rechaza, lo que puede sorprender a un operador que use la coma decimal. Decida si ese rechazo debería producir un mensaje claro en lugar de una negativa silenciosa.
8.7 Errores comunes y límites
- Comprobaciones de rango que dejan pasar NaN.
- Capturar todas las excepciones y continuar.
- Validar solo en la interfaz de usuario y no donde se ejecuta la orden.
- Pruebas que solo cubren valores típicos.
- Creer que la validación hace auténtico un canal: una orden falsificada de apariencia válida sigue pasando (el capítulo 17 añade la autenticación).
En CW el tercer error es el peligroso: si solo la interfaz de operador comprueba los puntos de consigna, cualquier otro cliente que pueda llegar al PLC por Modbus evita por completo la validación, como muestra el conducto de la zona de campo del Lab 4. El quinto error enlaza con el Lab 8: un punto de consigna bien formado de 2,0 m pasa la v2 tanto si lo envió un operador como si lo envió un intruso.
Ejercicios — capítulo 8
EX015 — Romper la v1. Objetivo: encontrar entradas que burlen una comprobación por lista de denegados. Entradas: accept_v1. Pasos: encuentre tres entradas distintas para las que la v1 da un resultado inseguro o indefinido y explique cada una. Evidencia esperada: las entradas y las explicaciones. Rúbrica: 3 = NaN aceptado; texto no numérico (o una cadena vacía) que lanza una excepción no gestionada; y un tercer caso distinto, como un valor actual NaN, que hace que la comprobación del paso se supere para cualquier punto de consigna dentro de rango; 2 = dos; 1 = una; 0 = nada.
EX016 — Especificar el estado de falla. Objetivo: conectar el código con la seguridad funcional del proceso. Entradas: un punto de consigna rechazado en una bomba de dosificación de cloro y en una válvula de entrada de agua bruta. Pasos: para cada caso, indique qué debe hacer el programa ante el rechazo, quién debe decidirlo y qué debe registrarse. Evidencia esperada: dos especificaciones breves. Rúbrica: 3 = conservar el último valor bueno más alarma, decide el responsable del proceso, campos del registro nombrados (hora, fuente, entrada bruta, regla que falló); 2 = parcial; 1 = genérico; 0 = nada.
Síntesis del capítulo
El análisis de la entrada es una frontera de seguridad. Defina con precisión la entrada válida, acepte solo esa, falle hacia un estado elegido por el responsable del proceso, registre cada rechazo y pruebe los límites y los casos mal formados. La validación detecta la entrada mal formada; no demuestra que una entrada bien formada procediera de alguien autorizado.