Libseccomp: ERRO: SCMP_CMP_GT / GE / LT / LE não funciona como esperado para argumentos syscall negativos

Criado em 18 jan. 2017  ·  20Comentários  ·  Fonte: seccomp/libseccomp

Oi!

Não tenho certeza se o comportamento atual de SCMP_CMP_GT / GE / LT / LE está funcionando conforme o esperado ou se há um bug em sua implementação. A página de manual de seccomp_rule_add tem apenas isto a dizer sobre SCMP_CMP_GT:

SCMP_CMP_GT:
        Matches when the argument value is greater than the datum value,
        example:

        SCMP_CMP( arg , SCMP_CMP_GT , datum )

A página do manual não especifica o tipo de datum e tem exemplos para vários tipos (implícitos) (e um cast para scmp_datum_t).

Com base na página do manual, eu esperava que algo assim funcionasse para qualquer valor dado ao terceiro argumento de setpriority (assuma a política padrão de SCMP_ACT_ALLOW para isso):

rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM),
        SCMP_SYS(setpriority),
        3,
        SCMP_A0(SCMP_CMP_EQ, PRIO_PROCESS),
        SCMP_A1(SCMP_CMP_EQ, 0),
        SCMP_A2(SCMP_CMP_GT, 0));

Em vez disso, setpriority(PRIO_PROCESS, 0, -1) resulta no bloqueio da syscall quando '-1' é obviamente menor que '0'. setpriority(PRIO_PROCESS, 0, 0) e setpriority(PRIO_PROCESS, 0, 1) funcionam como esperado. O que está acontecendo é que '-1' está sendo convertido para scmp_datum_t (uint64_t de secomp.h.in), o que obviamente torna isso positivo, mas SCMP_CMP_GT e seus amigos não estão lidando com essa conversão. SCMP_CMP_EQ funciona bem com um datum negativo (supondo que o datum ainda é positivo (eu não verifiquei), mas a comparação é entre scmp_datum_t convertido).

Este comportamento foi confirmado com 2.1.0 + dfsg-1 (Ubuntu 14.04 LTS, 3.13 kernel), 2.2.3-3ubuntu3 (Ubuntu 16.04 LTS, 4.9 kernel), 2.3.1-2ubuntu2 (Ubuntu 17.04 dev release, 4.9 kernel) e master de alguns momentos atrás (no Ubuntu 17.04 dev release, 4.9 kernel), tudo em amd64.

AFAICT, não há testes para SCMP_CMP_GT e SCMP_CMP_LE. Os poucos testes para SCMP_CMP_LT não parecem levar em conta os valores negativos e nem o faz para SCMP_CMP_GE (corrija-me se eu estiver errado).

A questão é então: esse comportamento é intencional? Se for assim, embora eu admita que pode ser argumentado que a página do manual é precisa, uma vez que estão funcionando perfeitamente corretamente ao entender que scmp_datum_t é o tipo de dados, esta situação não é imediatamente clara e a página do manual provavelmente deve dizer que os aplicativos precisam levar em conta isto. Caso contrário, isso parece ser um bug na implementação de SCMP_CMP_GT / GE / LT / LE.

Aqui está um pequeno programa que demonstra esse problema com SCMP_CMP_GT, embora GE, LT e LE possam ter o mesmo comportamento:

/*
 * gcc -o test-nice test-nice.c -lseccomp
 * sudo ./test-nice 0 1  # should be denied
 * sudo ./test-nice 0 0  # should be allowed
 * sudo ./test-nice 0 -1 # should be allowed?
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <seccomp.h>
#include <sys/resource.h>

int main(int argc, char **argv)
{
    if (argc < 3) {
        fprintf(stderr, "test-nice N N\n");
        return 1;
    }

    int rc = 0;
    scmp_filter_ctx ctx = NULL;
    int filter_n = atoi(argv[1]);
    int n = atoi(argv[2]);

    // Allow everything by default for this test
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (ctx == NULL)
        return ENOMEM;

    printf("set EPERM for nice(>%d)\n", filter_n);
    rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM),
            SCMP_SYS(setpriority),
            3,
            SCMP_A0(SCMP_CMP_EQ, PRIO_PROCESS),
            SCMP_A1(SCMP_CMP_EQ, 0),
            SCMP_A2(SCMP_CMP_GT, filter_n));

    if (rc != 0) {
        perror("seccomp_rule_add failed");
        goto out;
    }

    rc = seccomp_load(ctx);
    if (rc != 0) {
        perror("seccomp_load failed");
        goto out;
    }

    // try to use the filtered syscall
    errno = 0;
    printf("Attempting nice(%d)\n", n);
    nice(n);
    if (errno != 0) {
        perror("could not nice");
        if (filter_n > n)
            fprintf(stderr, "nice(%d) unsuccessful. bug?\n", n);
        rc = 1;
        goto out;
    } else
        printf("nice(%d) successful\n", n);

out:
    seccomp_release(ctx);

    return rc;
}
bug prioritmedium

Todos 20 comentários

Obrigado pelo relatório do problema; essa é boa.

Por acaso, você tentou escrever o reprodutor usando os cabeçalhos / macros no diretório samples / seccomp do kernel?

Tive a impressão de que o código BPF no kernel tratava os valores imediatos como assinados; pode não ser o caso, ou posso ter estragado algo no código libseccomp.

FWIW, o próprio BPF usa u32 para seus argumentos. O libseccomp assina a extensão nos argumentos compat? (Provavelmente não deveria, mas as regras para corresponder a "-1" devem ser diferentes entre 32 bits e 64 bits ...)

O problema que está me preocupando agora são as comparações do BPF GT / GE no operador de salto, especialmente porque eu suspeito que quase todo mundo está tratando o BPF imediato como um valor assinado para essas comparações.

@kees, qual é a abordagem recomendada para fazer comparações assinadas de argumentos syscall com a máquina seccomp-bpf do kernel? Espero que não seja algo do tipo "verifique primeiro o bit mais alto e, em seguida, faça a conversão de dois elogios necessária antes de comparar os números negativos". Embora seja irritante, podemos sempre alterar o libseccomp para gerar o BPF necessário (embora os filtros gerados agora sejam muito maiores em alguns casos), mas eu me preocupo com os aplicativos que criam seus próprios filtros BPF; as chances de eles lidarem com isso corretamente provavelmente não são muito boas.

Infelizmente, uma vez que os argumentos syscall são "longos sem sinal" (veja syscall_get_arguments () e struct seccomp_data), não há nenhum caso comum de como um syscall trata as conversões de sinais. Algumas syscalls ao cruzar a barreira de compatibilidade farão extensão de sinal, outras (prctl) não. Existem muitos argumentos de syscall negativos-mas-não-menos-um?

Voltando a isso hoje, e tendo brincado com as coisas um pouco mais esta manhã, acho que isso vai acabar como uma documentação / "tenha cuidado!" problema, pois não há uma boa solução, especialmente quando estamos falando de usuários existentes. Deixe-me tentar fornecer alguns antecedentes / explicação da libseccomp para acompanhar os comentários úteis de @kees do lado do kernel.

FWIW, o próprio BPF usa u32 para seus argumentos. O libseccomp assina a extensão nos argumentos compat? (Provavelmente não deveria, mas as regras para corresponder a "-1" devem ser diferentes entre 32 [bits e 64 bits ...)

As funções de regra da API libseccomp interpretam todas assumem valores imediatos como _uint64_t_, portanto, se você for descuidado com seus tipos / conversão, poderá ter problemas. Exemplo:

$ cat 00-test.c
    /* ... */
    seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1000, 1,
                           SCMP_A0(SCMP_CMP_GT, -1));
    seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1001, 1,
                           SCMP_A0(SCMP_CMP_GT, (uint32_t)-1));
    seccomp_rule_add_exact(ctx, SCMP_ACT_KILL, 1002, 1,
                           SCMP_A0(SCMP_CMP_GT, 0xffffffff));
    /* ... */
$ make 00-test
  CC       00-test.o
  CCLD     00-test
$ ./00-test -p
  #
  # pseudo filter code start
  #
  # filter for arch x86_64 (3221225534)
  if ($arch == 3221225534)
    # filter for syscall "UNKNOWN" (1002) [priority: 65533]
    if ($syscall == 1002)
      if ($a0.hi32 >= 0)
        if ($a0.lo32 > 4294967295)
          action KILL;
    # filter for syscall "UNKNOWN" (1001) [priority: 65533]
    if ($syscall == 1001)
      if ($a0.hi32 >= 0)
        if ($a0.lo32 > 4294967295)
          action KILL;
    # filter for syscall "UNKNOWN" (1000) [priority: 65533]
    if ($syscall == 1000)
      if ($a0.hi32 >= 4294967295)
        if ($a0.lo32 > 4294967295)
          action KILL;
    # default action
    action ALLOW;
  # invalid architecture action
  action KILL;
  #
  # pseudo filter code end
  # 
$ ./00-test -b | ../tools/scmp_bpf_disasm 
   line  OP   JT   JF   K
  =================================
   0000: 0x20 0x00 0x00 0x00000004   ld  $data[4]
   0001: 0x15 0x00 0x0c 0xc000003e   jeq 3221225534 true:0002 false:0014
   0002: 0x20 0x00 0x00 0x00000000   ld  $data[0]
   0003: 0x35 0x0a 0x00 0x40000000   jge 1073741824 true:0014 false:0004
   0004: 0x15 0x00 0x02 0x000003e8   jeq 1000 true:0005 false:0007
   0005: 0x20 0x00 0x00 0x00000014   ld  $data[20]
   0006: 0x35 0x04 0x06 0xffffffff   jge 4294967295 true:0011 false:0013
   0007: 0x15 0x01 0x00 0x000003e9   jeq 1001 true:0009 false:0008
   0008: 0x15 0x00 0x04 0x000003ea   jeq 1002 true:0009 false:0013
   0009: 0x20 0x00 0x00 0x00000014   ld  $data[20]
   0010: 0x35 0x00 0x02 0x00000000   jge 0    true:0011 false:0013
   0011: 0x20 0x00 0x00 0x00000010   ld  $data[16]
   0012: 0x25 0x01 0x00 0xffffffff   jgt 4294967295 true:0014 false:0013
   0013: 0x06 0x00 0x00 0x7fff0000   ret ALLOW
   0014: 0x06 0x00 0x00 0x00000000   ret KILL

... como podemos ver, se você usar a conversão apropriada, o valor não terá extensão de sinal. No entanto, espero que não seja isso o que a maioria das pessoas está fazendo. A boa notícia é que imagino que o número de syscalls que levam argumentos negativos seja relativamente pequeno, então o impacto deve ser um pouco limitado.

Indo em frente, definitivamente precisamos colocar algo na documentação sobre isso e ver se podemos fazer algo para tornar a vida mais fácil para os desenvolvedores, talvez implementar variantes de 32 bits das macros _SCMP_A * _.

@pcmoore - obrigado pela resposta detalhada e desculpe por não voltar antes. Não, eu não tentei escrever um reprodutor baseado em https://github.com/torvalds/linux/tree/master/samples/seccomp ainda, mas com base em seus comentários, parece que não preciso. Avise-me se precisar de mais alguma coisa. Por enquanto, vou adotar a abordagem de 'ser cuidadoso' e relatar se eu tiver quaisquer problemas, e espero saber como você pode tornar isso mais fácil de consertar no futuro.

@jdstrand Acho que estamos prontos por enquanto. Obrigado mais uma vez pelo relatório. Lamento não ter uma resposta melhor para você, mas espero que tenhamos algo no futuro.

Nesse ínterim, se você tiver problemas uma vez com um valor de elenco de tipo adequado, sinta-se à vontade para atualizar este problema.

A boa notícia é que imagino que o número de syscalls que levam argumentos negativos seja relativamente pequeno, então o impacto deve ser um pouco limitado.

Acabei de ter esse problema ao verificar (entre outras coisas) se o parâmetro fd de openat () é igual ao valor especial AT_FDCWD que é -100. Isso leva a:

  # filter for syscall "openat" (257) [priority: 131067]
  if ($syscall == 257)
    if ($a0.hi32 == 4294967295)
      if ($a0.lo32 == 4294967196)
        if ($a2.hi32 & 0x00000000 == 0)
          if ($a2.lo32 & 0x00000003 == 0)
            action ERRNO(2);

Onde deveria estar:

  # filter for syscall "openat" (257) [priority: 131067]
  if ($syscall == 257)
    if ($a0.hi32 == 0)
      if ($a0.lo32 == 4294967196)
        if ($a2.hi32 & 0x00000000 == 0)
          if ($a2.lo32 & 0x00000003 == 0)
            action ERRNO(2);

Como a glibc 2.26+ parece usar exclusivamente o openat syscall com AT_FDCWD para implementar open (), isso pode confundir muitas pessoas. Aplicar um elenco a uint32_t conforme sugerido acima resolveu o problema para mim:

        // selector, action, syscall, no of args, args
        { SEL, SCMP_ACT_ERRNO(ENOENT), "openat", 2,
-               { SCMP_A0(SCMP_CMP_EQ, AT_FDCWD), /* glibc 2.26+ */
+               { SCMP_A0(SCMP_CMP_EQ, (uint32_t)AT_FDCWD), /* glibc 2.26+ */
                  SCMP_A2(SCMP_CMP_MASKED_EQ, O_ACCMODE, O_RDONLY) }},

Seria bom ter um SCMP_A0_U32 explícito.

@drakenclimber @jdstrand @michaelweiser o que vocês acham de https://github.com/pcmoore/misc-libseccomp/commit/b9ce39d776ed5a984c7e9e6db3b87463edce82a7 como uma solução para isso?

@pcmoore : Obrigado por continuar investigando isso! Acabei de dar uma olhada e parece muito bom no código:

static struct {
        const uint64_t promises;
        const uint32_t action;
        const char *syscall;
        const int arg_cnt;
        const struct scmp_arg_cmp args[3];
} scsb_calls[] = {
[...]
        { PLEDGE_WPATH, SCMP_ACT_ALLOW, "openat", 2, /* glibc 2.26+ */
                { SCMP_A0_32(SCMP_CMP_EQ, AT_FDCWD),
                  SCMP_A2_64(SCMP_CMP_MASKED_EQ, O_ACCMODE, O_WRONLY) }},

Infelizmente, parece que a função auxiliar não é adequada como um inicializador de estrutura:

In file included from pledge.c:42:
/include/seccomp.h:230:26: error: initializer element is not constant
 #define SCMP_CMP32(...)  (__scmp_arg_32(SCMP_CMP64(__VA_ARGS__)))
                          ^
/include/seccomp.h:241:26: note: in expansion of macro ‘SCMP_CMP32’
 #define SCMP_A0_32(...)  SCMP_CMP32(0, __VA_ARGS__)
                          ^~~~~~~~~~
pledge.c:188:5: note: in expansion of macro ‘SCMP_A0_32’
   { SCMP_A0_32(SCMP_CMP_EQ, AT_FDCWD),
     ^~~~~~~~~~
/include/seccomp.h:230:26: note: (near initialization for ‘scsb_calls[21].args[0]’)
 #define SCMP_CMP32(...)  (__scmp_arg_32(SCMP_CMP64(__VA_ARGS__)))
                          ^
/include/seccomp.h:241:26: note: in expansion of macro ‘SCMP_CMP32’
 #define SCMP_A0_32(...)  SCMP_CMP32(0, __VA_ARGS__)
                          ^~~~~~~~~~
pledge.c:188:5: note: in expansion of macro ‘SCMP_A0_32’
   { SCMP_A0_32(SCMP_CMP_EQ, AT_FDCWD),
     ^~~~~~~~~~

Obrigado pela revisão @michaelweiser , infelizmente não achei que as pessoas estivessem usando essas macros como inicializadores, mas é um uso válido e com certeza algumas pessoas estão usando dessa forma.

Vou precisar pensar um pouco sobre isso ... você tem alguma ideia de como resolver isso de uma maneira elegante?

Não faço ideia, desculpe, eu já estava com os olhos abertos com fósforos. :)

Olhando para ele agora, acho que vejo o problema: devido às listas de argumentos de variáveis, você não pode injetar as conversões necessárias, certo?

Pode scmp_arg_cmp talvez conter uma união dando diferentes visões sobre os dados com a largura correta, alinhamento (e talvez até mesmo ordem de bytes) (que IMO meio que entra em conflito com "elegante") :? Se for puramente interno ao libseccomp e não precisar ser compatível com uma interface do kernel, poderia carregar um indicador de tipo de dados como um campo separado e ter as funções de usuário classificando-o? E isso poderia ser inicializado usando varargs?

Caso contrário, em vez de marcar a operação como um bit 32/64 inteiro, os operandos poderiam ser anotados, envolvendo um elenco e dando recomendação severa ao usuário (como você faz) para sempre usar essas anotações sob pena de encontrar problemas difíceis de depurar ?

{ SCMP_A0(SCMP_CMP_EQ, SCMP_OP_32(AT_FDCWD)),
  SCMP_A2(SCMP_CMP_MASKED_EQ, SCMP_OP_64(O_ACCMODE), SCMP_OP_64(O_WRONLY)) }},

ou

{ SCMP_A0(SCMP_CMP_EQ, SCMP_OP1_32(AT_FDCWD)),
  SCMP_A2(SCMP_CMP_MASKED_EQ, SCMP_OP2_64(O_ACCMODE, O_WRONLY)) }},

Eu não sou o suficiente de um pré-processador de crack para inventar muito mais, desculpe.

@pcmoore , as mudanças parecem boas para mim. Não sou especialista em pré-processador, mas vou dar uma olhada no problema que @michaelweiser mencionou acima

Olhando para ele agora, acho que vejo o problema: devido às listas de argumentos de variáveis, você não pode injetar as conversões necessárias, certo?

Sim, é basicamente isso. Talvez haja uma maneira não terrível de contornar isso, mas ainda não encontrei uma.

Pode scmp_arg_cmp talvez conter uma união dando diferentes visões sobre os dados com a largura correta, alinhamento (e talvez até mesmo ordem de bytes) (que IMO meio que entra em conflito com "elegante") :? Se for puramente interno ao libseccomp e não precisar ser compatível com uma interface do kernel, poderia carregar um indicador de tipo de dados como um campo separado e ter as funções de usuário classificando-o? E isso poderia ser inicializado usando varargs?

Temos um problema em que a estrutura scmp_arg_cmp é parte da API libseccomp, portanto, a menos que desejemos alterar a versão principal da libseccomp, não podemos realmente alterar o tamanho da estrutura ou o deslocamento de qualquer um dos campos de membro; fazer isso quebraria a interface binária existente com os aplicativos existentes. Converter os campos de dados de 64 bits em uma união contendo um valor de 64 ou 32 bits deve funcionar por si só, mas você também precisa adicionar algumas informações adicionais à estrutura scmp_arg_cmp para indicar qual membro da união usar ; é esse sinalizador extra que pode ser problemático.

Pode ser possível roubar alguns bits dos campos "arg" ou "op", ambos são valores de 32 bits e estão usando apenas uma fração desse espaço. No entanto, considero essa uma opção bastante extrema e gostaria de evitar isso, se possível.

Caso contrário, em vez de marcar a operação como um bit 32/64 inteiro, os operandos poderiam ser anotados, envolvendo um elenco e dando recomendação severa ao usuário (como você faz) para sempre usar essas anotações sob pena de encontrar problemas difíceis de depurar ?

Eu não entendo muito bem o que ganharíamos envolvendo os operandos com uma macro, você pode elaborar um pouco mais? Poderíamos fornecer uma macro para envolver os valores de referência, mas isso não é realmente diferente do que apenas pedir aos chamadores para fornecerem a conversão adequada.

@pcmoore , as mudanças parecem boas para mim. Não sou especialista em pré-processador, mas vou dar uma olhada no problema que @michaelweiser mencionou acima

Ótimo, obrigado. Esperançosamente nós três podemos chegar a algo útil aqui.

@pcmoore : Olhando para http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/ e http://efesx.com/2010/08/31/overloading- macros / eu vim com o seguinte:

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
#define macro_dispatcher(func, ...) \
            macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__))
#define macro_dispatcher_(func, nargs) \
            macro_dispatcher__(func, nargs)
#define macro_dispatcher__(func, nargs) \
            func ## nargs

#define SCMP_CMP64(...)         ((struct scmp_arg_cmp){__VA_ARGS__})

#define SCMP_CMP32_1(x)                 SCMP_CMP64(x)
#define SCMP_CMP32_2(x, y)              SCMP_CMP64(x, y)
#define SCMP_CMP32_3(x, y, z)           SCMP_CMP64(x, y, (uint32_t)(z))
#define SCMP_CMP32_4(x, y, z, q)        SCMP_CMP64(x, y, (uint32_t)(z), (uint32_t)(q))
#define SCMP_CMP32(...) macro_dispatcher(SCMP_CMP32_, __VA_ARGS__)(__VA_ARGS__)

#define SCMP_A0_64(...)         SCMP_CMP64(0, __VA_ARGS__)
#define SCMP_A0_32(...)         SCMP_CMP32(0, __VA_ARGS__)

Para este caso de teste:

        struct scmp_arg_cmp f[] = {
                SCMP_A0_64(SCMP_CMP_EQ, 1, 20),
                SCMP_A0_32(SCMP_CMP_EQ, 2, 3),
                SCMP_A0_32(SCMP_CMP_LT, 2),
        };

sai de gcc-7.4.0 -E e clang-7 -E como:

 struct scmp_arg_cmp f[] = {
  ((struct scmp_arg_cmp){0, SCMP_CMP_EQ, 1, 20}),
  ((struct scmp_arg_cmp){0, SCMP_CMP_EQ, (uint32_t)(2), (uint32_t)(3)}),
  ((struct scmp_arg_cmp){0, SCMP_CMP_LT, (uint32_t)(2)}),
 };

Supondo que SCMP_A[0-5]_43 precisa de pelo menos op para funcionar e SCMP_CMP32 requer arg , duas linhas podem ser salvas tornando esses parâmetros posicionais:

#define SCMP_CMP32_1(x, y, z)           SCMP_CMP64(x, y, (uint32_t)(z))
#define SCMP_CMP32_2(x, y, z, q)        SCMP_CMP64(x, y, (uint32_t)(z), (uint32_t)(q))
#define SCMP_CMP32(x, y,...)            macro_dispatcher(SCMP_CMP32_, __VA_ARGS__)(x, y, __VA_ARGS__)

#define SCMP_A0_32(x,...)       SCMP_CMP32(0, x, __VA_ARGS__)

Muito bem @michaelweiser! Você gostaria de fazer um RP para que possamos revisar / comentar as mudanças com mais facilidade? Se não, tudo bem, vou fazer um e me certificar de que você receba muitos créditos :)

Vou fazer o projeto de relações públicas esta noite. Além de https://github.com/pcmoore/misc-libseccomp/commit/b9ce39d776ed5a984c7e9e6db3b87463edce82a7 ou do zero?
Como creditamos ao Blogger Roman por sua solução de sobrecarga? Encontrou o que parece ser a casa atual de seu blog em https://kecher.net/overloading-macros/. Sua postagem parece exatamente a fonte original. Comente com link para a postagem acima da lógica macro_dispatcher ?

Vou fazer o projeto de relações públicas esta noite. No topo do pcmoore @ b9ce39d ou do zero?

Ótimo, obrigado! Vá em frente e baseie-o no branch master, eu nunca mesclei as coisas na minha árvore misc-libseccomp e não planejo fazer isso neste ponto, já que sua abordagem é muito melhor.

Como creditamos ao Blogger Roman por sua solução de sobrecarga? Encontrou o que parece ser a casa atual de seu blog em https://kecher.net/overloading-macros/. Sua postagem parece exatamente a fonte original. Comente com link para a postagem acima da lógica macro_dispatcher ?

Geralmente não creditamos as pessoas diretamente na fonte, a menos que haja algum requisito de licença; Eu recomendaria adicionar um comentário na descrição do patch creditando Roman com a ideia básica e fornecendo um link para sua postagem no blog. Não vejo nenhuma licença ou restrição colocada em seus exemplos, então não acredito que haja qualquer problema a esse respeito, e com base em uma amostra de seu blog, acredito que sua intenção é compartilhar essas ideias com outras pessoas (como nós ) para ajudá-los a resolver seus problemas. Se você tiver um endereço de e-mail para Roman, você pode sempre tentar enviar algum e-mail para ele; se não conseguirmos entrar em contato com ele por qualquer motivo, acho que não há problema em prosseguir.

Resolvido via 80a987d6f8d0152def07fa90ace6417d56eea741.

Esta página foi útil?
0 / 5 - 0 avaliações