◇ OpenCyber Academy
Engenharia · Capítulo 28 · Português

Capítulo 28 — Segurança de TO, IoT e sistemas ciberfísicos: implementação I

Ensino gratuito e aberto para quem deseja proteger pessoas e tecnologias. Não é preciso criar uma conta para estudar ou consultar as respostas.

Capítulo 28 — Segurança de TO, IoT e sistemas ciberfísicos: implementação I

Mapa da aula

Aula 28 publicada. Mapa, exemplo, EX082.

flowchart TD
  N0["28.1 Forma da implementação"]
  N1["28.2 Testes por cláusula"]
  N2["28.3 Logging"]
  N3["Nota prática"]
  N4["28.1b Catálogo de motivos e fuzzing"]
  N5["Nota de campo — achados do fuzzing"]
  N6["Síntese"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
  N6 --> EX["Exemplo resolvido"]
  EX --> Q["Exercício EX082"]

Desenho lógico

flowchart LR
  N0["28.1 Forma da implementação"]
  N1["28.2 Testes por cláusula"]
  N2["28.3 Logging"]
  N3["Nota prática"]
  N4["28.1b Catálogo de motivos e fuzzing"]
  N5["Nota de campo — achados do fuzzing"]
  N6["Síntese"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX082"]
[28.1 Forma da implementação]
    |
    v
[28.2 Testes por cláusula]
    |
    v
[28.3 Logging]
    |
    v
[Nota prática]
    |
    v
[28.1b Catálogo de motivos e fuzzing]
    |
    v
[Nota de campo — achados do fuzzing]
    |
    v
[Síntese]
    |
    v
[Exemplo resolvido] --> [Exercício EX082]

Objetivos de aprendizagem

Você vai (1) implementar permit() com códigos de motivo explícitos; (2) acrescentar testes para cada cláusula; (3) registrar logs sem armazenar segredos.

28.1 Forma da implementação

Função pura: entradas origem, código de função, registrador e AllowListVersion, saída (allow: bool, reason: str). Nenhuma E/S de rede dentro do predicado. Isso mantém os testes rápidos e livres de contato com a planta.

A função permit de Lena na ABP segue exatamente essa forma: recebe os campos do quadro decodificado e a versão da lista de permissões e devolve uma decisão com um motivo. A carga da lista de permissões e a escrita dos logs acontecem fora da função, então os seus testes rodam em milissegundos sobre dados sintéticos e nunca arriscam contato com a planta.

28.2 Testes por cláusula

Um arquivo de teste por cláusula: permissão da estação, código de função, faixa de registradores, política indisponível. Apenas dados de teste sintéticos. Mutação: quebre uma cláusula e veja o teste falhar.

"""Lab: Modbus function-code allow-list on synthetic, already-decoded requests (no network)."""
ALLOW = {("HMI-1", 3), ("HMI-1", 6), ("HIST-1", 3), ("HIST-1", 4)}  # (source, function code)
WRITE_REGS = {("HMI-1", 6): range(40001, 40011)}                     # HMI may write setpoints only
reqs = [("HMI-1", 3, 40001), ("HMI-1", 6, 40003), ("HMI-1", 6, 40050),
        ("HIST-1", 3, 30001), ("HIST-1", 16, 40001), ("ENG-WS", 8, 0), ("HIST-1", 4, 30010)]
for src, fc, reg in reqs:
    if (src, fc) not in ALLOW:
        d = "BLOCK fc-not-allowed"
    elif (src, fc) in WRITE_REGS and reg not in WRITE_REGS[(src, fc)]:
        d = "BLOCK register-out-of-range"
    else:
        d = "PASS"
    print(f"{src:7} fc={fc:2} reg={reg:5} -> {d}")

Saída esperada:

HMI-1   fc= 3 reg=40001 -> PASS
HMI-1   fc= 6 reg=40003 -> PASS
HMI-1   fc= 6 reg=40050 -> BLOCK register-out-of-range
HIST-1  fc= 3 reg=30001 -> PASS
HIST-1  fc=16 reg=40001 -> BLOCK fc-not-allowed
ENG-WS  fc= 8 reg=    0 -> BLOCK fc-not-allowed
HIST-1  fc= 4 reg=30010 -> PASS

Lena mantém um arquivo de teste por cláusula para a função permit da ABP, com os dados de teste nomeados pelo requisito que conferem. Os testes de mutação confirmaram que cada arquivo falha quando a sua cláusula é quebrada: apagar a verificação da faixa de registradores, por exemplo, fez os testes de fora da faixa falharem na hora. Uma cláusula cujos testes sobrevivem a uma mutação é tratada como não testada.

28.3 Logging

Registre a decisão, o motivo, a versão da política e o ator. Não registre senhas nem payloads completos de quadros que possam conter dados proprietários de receitas. Redija o que for necessário.

Caso sintético — Ashford Batch Plant. A implementação I entregou códigos de motivo que a interface de linha do tempo mostra — os operadores finalmente viram por que uma escrita foi bloqueada às 03:12.

Os logs de Lena mostram qual versão da política tomou cada decisão. Os payloads dos quadros ficam de fora porque podem conter valores de receitas. Lena confere uma amostra dos logs depois de cada versão, para confirmar que nada sensível escapou.

28.4 Erros e limites

Motivos em texto livre derivam; use um enum compartilhado com o painel de cobertura.

A implementação de Lena usa uma lista compartilhada de códigos de motivo, como register-out-of-range e policy-unavailable, publicada como documento versionado. O painel de cobertura e a visão de linha do tempo leem a mesma lista, então um código novo não pode aparecer nos logs sem ser entendido nos outros lugares. Os consumidores que veem um código desconhecido o tratam como negação e geram um alerta para o dono da ferramenta. O documento é revisto a cada mudança do predicado.

Nota prática

Faça fuzzing do predicado com estações, códigos e UTF-8 quebrado aleatórios. Ele precisa negar (DENY) ou dar erro de forma fechada, nunca permitir (ALLOW) numa falha de parsing.

28.1b Catálogo de motivos e fuzzing

O catálogo lista, para cada código de motivo, a cláusula que o emite e o teste que o fixa: fc-not-allowed, register-out-of-range, policy-unavailable, station-unknown. As entradas de fuzzing que um dia encontraram um defeito entram numa lista de regressão reproduzida em cada versão, incluindo o nome de estação em UTF-8 que um dia derrubou o decodificador.

A lista de verificação de Lena para uma mudança no predicado do monitor cobre a nova cláusula, novos testes, a documentação dos códigos de motivo, um caso de fuzzing e a falha fechada em erro de parsing. Um revisor que aprova uma mudança de predicado sem a lista verificou o autor, não a mudança.

A lista de verificação de Lena é anexada a toda revisão do predicado.

Nota de campo — achados do fuzzing

Um harness de fuzzing de cinco minutos lançou um nome de estação em UTF-8 que antes fazia o decodificador cair numa exceção não tratada — que o chamador tratava como ALLOW. A correção passou a retornar DENY parse-error, e o harness agora roda na integração contínua a cada mudança do predicado.

28.5 Exemplo resolvido — um teste de cláusula que pegou um defeito real (ABP sintética)

Cada cláusula da função permit tem o seu próprio teste: origem permitida, código de função permitido, registrador na faixa, negação por padrão. O harness de fuzzing de Lena então alimenta 10.000 triplas aleatórias (origem, função, registrador), tiradas de um gerador com semente, e afirma que todo PASS satisfaz todas as cláusulas. Na primeira execução, ele descobriu que o código de função 23 (leitura/escrita de múltiplos registradores) passava para a HMI-1, porque a implementação verificava apenas a metade de leitura do pedido. A correção rejeita o 23 a menos que ele esteja listado explicitamente, um teste de cláusula agora o fixa, e a nota do incidente registra qual semente reproduziu a falha, para que qualquer pessoa possa rodá-la de novo.

Exercícios

Síntese

Predicado puro, testes por cláusula, logs seguros.

Continue estudando — capítulo 28 de 50

← Capítulo 27 · Índice de capítulos do curso · Capítulo 29 →

Siga para a próxima lição escrita.