Diário Qubes OS 04 - Compartilhamento de tela online

Publicado em 2017-07-11

Esse é o quarto post de uma série analisando o Qubes OS. Comece pelo primeiro post.


Após fazer a Yubikey funcionar e ter uma instalação do Windows pronta, a última coisa que gostaria de ter funcionando antes de migrar definitivamente para o Qubes OS é a possibilidade de realizar compartilhamentos e gravações de tela.

Compartilhamento de tela é essencial para o meu workflow porque trabalhando remotamente, é muito comum ter que mostrar ou discutir algum código, processo ou design com outras pessoas. E screencasts para poder fazer gravações de tutoriais e problemas.

A dificuldade no Qubes é conseguir realizar essa atividade sem comprometer a segurança do sistema.

Será que vai?

Em um software de conferência tradicional (digamos Skype rodando sob Windows) o software em execução tem acesso (pelo menos em princípio) a todo o seu sistema. Ele é capaz de realizar capturas de tela usando APIs nativas (como explicado aqui) e após codificação realizar transmissão com um parceiro remoto.

Analisando do ponto de vista de segurança, qualquer software executado nesse sistema pode iniciar a captura da tela quando quiser e fazer o que bem entender com ela. Isso significa que uma vulnerabilidade em qualquer software do sistema compromete tudo o que é exibido na tela do computador (inclusive potencialmente senhas, dados confidenciais, etc).

Dentro do modelo de segurança do Qubes, isso é possível apenas se o software for executado no dom0, que é totalmente desaconselhado. Mas nesse caso, como é possível realizar transmissões de tela pelo Qubes?

Layout da Solução

Navegando pelas listas de issues do Qubes no Github e pelas listas de discussão, outras pessoas também tiveram essa ideia mas nenhuma opção para transmissão em tempo real ([1], [2], [3], [4], [5]).

Mas um dos comentários do último link (issue 953 no Github) apresenta um possível layout de solução onde seria possível capturar a tela no dom0 utilizando o ffmpeg e de que essa solução seria (em primeira análise) segura porque o input ao ffmpeg rodando no dom0 seria apenas o bitmap do desktop - para explorar uma vulnerabilidade seria necessário que os bits de conteúdo de uma imagem fossem capazes de ativar essa vulnerabilidade (algo como o Snow Crash.) o que é improvável.

Munido dessa dica (e o fato de já ter usado o vlc/ffmpeg para captura de tela uma vez), comecei a explorar como implementar essa solução de forma segura e cheguei no seguinte conjunto de softwares:

  • ffmpeg: Usado no dom0 para fazer a captura do desktop e em uma AppVM para fazer a codificação para um codec de vídeo qualquer;
  • video4linux (v4l2): É o conjunto de drivers/protocolos/APIs que permite a captura de video em tempo real no Linux;
  • v4l2loopback: Um kernel module que cria um dispositivo que funciona como uma webcam virtual.

A ideia básica da solução consiste no seguinte:

Layout da solução de screencast

No dom0, o ffmpeg é executado fazendo a captura da tela através do dispositivo de entrada x11grab. A saída é emitida em modo rawvideo (ou seja, video cru sem nenhum codec específico) com resolução pré-determinada.

Na AppVM que irá rodar o software de teleconferência é instalado o módulo kernel v4l2loopback. Isso cria um dispositivo /dev/video0 que aceita vídeo no formato v4l2. Nessa mesma AppVM, o ffmpeg é executado, convertendo do formato rawvideo para o formato v4l2 e fazendo a saída no dispositivo loopback.

A saída do dom0 é direcionada para a AppVM através de um canal Qubes RPC que liga o stdin do processo ffmpeg no dom0 com o ffmpeg da AppVM, permitindo a troca de informações.

O script básico executado no dom0 para realizar essa ligação é o seguinte:

#!/usr/bin/sh

qvm-run -p \
  --localcmd="/home/matheus/ffmpeg-static/ffmpeg \
    -f x11grab -r 15 -s 800x600 -i :0.0+0,0 \
    -pix_fmt yuv420p -threads 0 -f rawvideo -" \
  untrusted \
  "sleep 3 ; /home/user/Downloads/ffmpeg-static/ffmpeg \
    -f rawvideo -s:v 800x600 -pix_fmt yuv420p -re -i pipe:  \
    -f v4l2 /dev/video0"

Destrinchando passo a passo o script:

  • qvm-run -p executa o comando a seguir passando o stdin/stdout entre o processo local (dom0) e o processo na AppVM (nesse caso na vm untrusted)
  • --localcmd= significa que a string a seguir é um comando a ser executado no dom0
  • -f x11grab -r 15 -s 800x600 -i :0.0+0,0: usar o driver x11grab do ffmpeg para capturar o desktop 0 a partir das coordendas (0,0) da tela, no tamanho 800x600 e a uma velocidade de 15 fps
  • -pix_fmt yuv420p -threads 0 -f rawvideo: formato de saída do vídeo como cru (sem compressão)
  • untrusted: executar o comando a seguir na AppVM chamada “untrusted”
  • -f rawvideo -s:v 800x600 -pix_fmt yuv420 -re -i pipe:: o arquivo de entrada é o stdin em um formato raw codificado como yuv420p e tamanho 800x600
  • -f v4l2 /dev/video0: saída para a webcam virtual no formato v4l2

O ponto mais crítico nesse script é acertar o formato de vídeo transferido via pipe entre o dom0 e a AppVM. Como o formato é cru (sem compressão) e sem metadata associado, ele precisa ser especificado explicitamente nos dois comandos para que o ffmpeg na VM seja capaz de recodificar com sucesso para o formato v4l2.

Caso seja necessário aumentar a resolução é necessário ajustar os dois comandos para a nova resolução (imagine o porque: sem a informação correta, o ffmpeg de destino não sabe onde começa e termina uma imagem em particular).

ffmpeg no dom0

A primeira dificuldade para executar o script acima é copiar o ffmpeg para o dom0. Infelizmente, não existe hoje um pacote nos repositórios do Qubes para fazer isso, então isso significa copiar manualmente.

Essa é a principal brecha de segurança dessa solução nesse momento: colocar uma compilação segura do ffmpeg no dom0 (imagine o que aconteceria se você baixasse uma cópia comprometida e a colocasse no dom0).

Em meu teste, utilizei a versão estática pré-compilada do ffmpeg (link) porém esse não é o método ideal já que não são fornecidas assinatudas GPG nem do binário nem do builder e de qualquer maneira estariamos confiando em toda a infraestrutura do builder para não gerar uma versão comprometida do binário.

Alternativas para resolver essa brecha seriam as seguintes:

  • Não usar o ffmpeg para realizar a captura mas algum outro software mais simples (se alguém conhecer alguma alternativa, entre em contato)
  • Ter o ffmpeg (ou a ferramenta do ponto anterior) em um repositório oficial do qubes
  • Compilar nós mesmos o ffmpeg e transferir para o dom0

Após compilar o ffmpeg, para transferir para o dom0 é simples:

[matheus@dom0]$ qvm-run -p dev 'cat /home/user/ffmpeg/bin/ffmpeg' > /home/matheus/ffmpeg

Ou algo similar (dependendo da localização do ffmpeg compilano na AppVM e onde você deseja colocá-lo no dom0).

Módulo v4l2loopback em uma AppVM

Aqui foi onde tive o maior trabalho em todo o processo: como instalar o módulo v4l2loopback em uma AppVM.

Como no Qubes o kernel das AppVMs é gerenciado pelo dom0 por padrão, a instalação do novo módulo tem que ser feita nele (documentação do Qubes). Mas o download do código e compilação do módulo devem ser preferencialmente feitas em uma AppVM.

Uma segunda dificuldade é que a versão do repo kernel-devel disponível para instalação nas AppVMs é diferente da versão do kernel utilizada pelo dom0 (no momento em que escrevo isso, o kernel-devel das VMs está na versão 4.4.31 e o kernel usado no dom0 para as AppVMs é o 4.4.67). Pra resolver isso, eu instalei manualmente o pacote de kernel headers na AppVM.

O processo completo é o seguinte:

  • uname -r para ver a versão atual do kernel na AppVM
  • Download do pacote kernel-devel-XXXX (onde XXXX é a versão do kernel visto) do ftp do Qubes
  • Instalar manualmente o rpm com o comando sudo dnf install kernel-devel...rpm)
  • Download (via git ou zip) o código do v4l2loopback.
  • Compilar o v4l2loopback com um comando make
  • Copiar o arquivo v4l2loopback.ko para o dom0 com o comando (no dom0) qvm-run -p dev '/home/user/Downloads/v4l2loopback-master/v4l2loopback.ko' > v4l2loopback.ko
  • Ajustar permissões do módulo chmod 0744 v4l2loopback.ko
  • Ajustar owner do módulo sudo chown root:root v4l2loopback.ko
  • Mover módulo para pasta do kernel usado na AppVM dom0$ sudo mv v4l2loopback.ko /usr/lib/modules/[versao]/extra
  • Efetuar sudo depmod [versao].pvops.qubes.x86_64
  • Instalar as ferramentas de produção de kernel no dom0 sudo qubes-dom0-update qubes-kernel-vm-support kernel-devel
  • Rebuildar kernel sudo qubes-prepare-vm-kernel [versao].pvops.qubes.x86_64 [versao].v4l2loopbback.qubes
  • Com a compilação bem sucedida do novo kernel, no gerenciador de VMs do qubes, mudar o kernel da VM onde as gravações e transmissões serão feitas para o novo kernel ([versa].v4l2loopback.qubes)
  • Reinicar a VM
  • Na AppVM carregar o módulo sudo modprobe v4l2loopback exclusive_caps=1 (o exclusive_caps parece ser necessário para funcionar com o chrome - veja aqui)
  • Caso tudo tenha dado certo, o dispositivo /dev/video0 terá sido criado

Testando a Webcam Virtual

Assumindo que o processo completo para instalação do módulo do kernel tenha dado certo e o dispositivo virtual tenha sido criado em /dev/video0, agora podemos testar a webcam virtual.

Em primeiro lugar, faça o download de um vídeo mp4 qualquer de testes (tem um disponível nessa página).

Agora inicie o ffmpeg de forma a mandar o vídeo para o dispositivo webcam virtual:

fmpeg -re -i video.mp4 -f v4l2 /dev/video0

Simulando uma webcam tocando um vídeo

Finalmente, verifique se a webcam está funcionando em algum programa. Caso queira testar pelo navegador, acesse um site de testes de webcam html5 como esse aqui.

Nota: No Chrome tive um problema em que precisei iniciar a transmissão antes de abrir o navegador e também utilizar a opção exclusive_caps=1 ao carregar o módulo kernel. No Firefox tive menos problemas e funcionou no primeiro teste.

Primeiro teste compartilhando o desktop

Transmitindo o Desktop

Finalmente para amarrar todas as pontas e ser capaz de transmitir o desktop do Qubes em um software de teleconferência, execute o comando abaixo em um terminal do dom0:

qvm-run -p \
  --localcmd="/home/matheus/bin/ffmpeg \
    -f x11grab -r 15 -s 800x600 -i :0.0+0,0 \
    -pix_fmt yuv420p -threads 0 -f rawvideo -" \
  untrusted \
  "sleep 3 ; /home/user/bin/ffmpeg \
    -f rawvideo -s:v 800x600 -pix_fmt yuv420p -re -i pipe:  \
    -f v4l2 /dev/video0"

Agora o desktop será exibido como uma webcam e poderá ser gravado. Veja abaixo um demo.


Esse foi o quarto post em uma série sobre o Qubes OS. No próximo post devo fazer uma revisão geral sobre as primeiras semanas de uso na prática.

Se você quer ver mais conteúdo técnico, assine meu feed RSS, minha newsletter, ou meu facebook.