Libseccomp: ERROR: SCMP_CMP_GT / GE / LT / LE no funciona como se esperaba para argumentos de llamada al sistema negativos

Creado en 18 ene. 2017  ·  20Comentarios  ·  Fuente: seccomp/libseccomp

¡Hola!

No estoy seguro de si el comportamiento actual de SCMP_CMP_GT / GE / LT / LE está funcionando según lo previsto o si hay un error en su implementación. La página de manual de seccomp_rule_add solo tiene esto que decir 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 )

La página de manual no especifica el tipo de datum y tiene ejemplos para varios tipos (implícitos) (y uno para scmp_datum_t).

Según la página del manual, esperaba que algo como esto funcionara para cualquier valor dado al tercer argumento de setpriority (asuma la política predeterminada de SCMP_ACT_ALLOW para esto):

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));

En cambio, setpriority(PRIO_PROCESS, 0, -1) da como resultado que la llamada al sistema se bloquee cuando '-1' es obviamente menor que '0'. setpriority(PRIO_PROCESS, 0, 0) y setpriority(PRIO_PROCESS, 0, 1) funcionan como se esperaba. Lo que está sucediendo es que '-1' se está convirtiendo a scmp_datum_t (uint64_t de secomp.h.in) lo que, por supuesto, lo hace positivo, pero SCMP_CMP_GT y sus amigos no están manejando esta conversión. SCMP_CMP_EQ funciona bien con un dato negativo (adivinar el dato sigue siendo positivo (no lo verifiqué), pero la comparación es entre scmp_datum_t convertido).

Este comportamiento se confirmó con 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) y master de hace unos momentos (en la versión de desarrollo de Ubuntu 17.04, kernel 4.9), todo en amd64.

AFAICT, no hay pruebas para SCMP_CMP_GT y SCMP_CMP_LE. Las pocas pruebas para SCMP_CMP_LT no parecen dar cuenta de valores negativos y tampoco la de SCMP_CMP_GE (corríjame si me equivoco).

La pregunta es entonces: ¿este comportamiento es intencional? Si es así, aunque admito que se podría argumentar que la página de manual es precisa, ya que funcionan perfectamente correctamente cuando se entiende que scmp_datum_t es el tipo de datos, esta situación no está clara de inmediato y la página de manual probablemente debería decir que las aplicaciones deben tener en cuenta esta. De lo contrario, esto parece ser un error en la implementación de SCMP_CMP_GT / GE / LT / LE.

Aquí hay un pequeño programa que demuestra este problema con SCMP_CMP_GT, aunque se puede observar que GE, LT y LE tienen el mismo comportamiento:

/*
 * 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 comentarios

Gracias por el informe del problema; Esa es buena.

Por casualidad, ¿ha intentado escribir el reproductor usando los encabezados / macros en el directorio samples / seccomp del kernel?

Tenía la impresión de que el código BPF del kernel trataba los valores inmediatos como firmados; puede que ese no sea el caso, o puede que haya estropeado algo en el código libseccomp.

FWIW, el propio BPF usa u32 para sus argumentos. ¿Libseccomp firma la extensión en los argumentos de compatibilidad? (Probablemente no debería, pero las reglas para coincidir con "-1" tienen que ser diferentes entre 32 bits y 64 bits ...)

El problema que me preocupa en este momento son las comparaciones de BPF GT / GE en el operador de salto, especialmente porque sospecho que la mayoría ha estado tratando el BPF inmediato como un valor firmado para estas comparaciones.

@kees ¿cuál es el enfoque recomendado para hacer comparaciones firmadas de argumentos syscall con la máquina seccomp-bpf del kernel? Espero que no sea algo como "verifique primero el bit alto y luego haga la conversión necesaria de dos antes de comparar números negativos". Si bien es molesto, siempre podemos cambiar libseccomp para generar el BPF necesario (aunque los filtros generados ahora serán mucho más grandes en algunos casos), pero me preocupan las aplicaciones que crean sus propios filtros BPF; las probabilidades de que manejen esto correctamente probablemente no sean muy buenas.

Desafortunadamente, dado que los argumentos de syscall son "unsigned long" (ver syscall_get_arguments () y struct seccomp_data), no existe un caso común de cómo un syscall maneja las conversiones de signos. Algunas llamadas al sistema al cruzar la barrera de compatibilidad harán una extensión de signo, otras (prctl) no. ¿Hay muchos argumentos de llamada al sistema negativos pero no menos uno?

Volviendo a esto hoy, y habiendo jugado un poco más con las cosas esta mañana, creo que esto terminará siendo una documentación / "¡ten cuidado!" problema ya que no hay una buena solución, especialmente cuando hablamos de usuarios existentes. Permítanme intentar proporcionar algunos antecedentes / explicación de libseccomp para @kees desde el lado del kernel.

FWIW, el propio BPF usa u32 para sus argumentos. ¿Libseccomp firma la extensión en los argumentos de compatibilidad? (Probablemente no debería, pero las reglas para coincidir con "-1" tienen que ser diferentes entre 32 [-bit y 64-bit ...)

Las funciones de reglas de la API de libseccomp interpretan todos los valores inmediatos como _uint64_t_, por lo que si no tiene cuidado con sus tipos / casting, puede tener problemas. Ejemplo:

$ 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, si usa la conversión adecuada, entonces el valor no es de signo extendido. Sin embargo, espero que esto no sea lo que la mayoría de la gente está haciendo. La buena noticia es que imagino que la cantidad de llamadas al sistema que toman argumentos negativos es relativamente pequeña, por lo que el impacto debería ser algo limitado.

En el futuro, definitivamente necesitamos incluir algo en los documentos sobre esto y ver si podemos hacer algo para facilitar la vida de los desarrolladores, tal vez implementar variantes de 32 bits de las macros _SCMP_A * _.

@pcmoore : gracias por la respuesta detallada y perdón por no haber vuelto antes. No, no intenté escribir un reproductor basado en https://github.com/torvalds/linux/tree/master/samples/seccomp todavía, pero según sus comentarios, parece que no es necesario. Déjame saber si necesitas algo más. Por ahora, tomaré el enfoque de "tenga cuidado" e informaré si tengo algún problema, y ​​espero con ansias cómo puede hacer que esto sea más fácil de solucionar en el futuro.

@jdstrand Creo que estamos todos listos por el momento. Gracias de nuevo por el informe, lamento no haber tenido una mejor respuesta para ti, pero espero que tengamos algo en el futuro.

Mientras tanto, si tiene algún problema una vez con un valor de conversión de tipo correcto, no dude en actualizar este problema.

La buena noticia es que imagino que la cantidad de llamadas al sistema que toman argumentos negativos es relativamente pequeña, por lo que el impacto debería ser algo limitado.

Me encontré con este problema al verificar (entre otras cosas) si el parámetro fd de openat () es igual al valor especial AT_FDCWD que es -100. Esto lleva 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);

Dónde debería 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);

Dado que glibc 2.26+ parece usar exclusivamente openat syscall con AT_FDCWD para implementar open (), esto podría hacer tropezar a mucha gente. Aplicar un molde a uint32_t como se sugirió anteriormente solucionó el problema para mí:

        // 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) }},

Seguro que sería bueno tener un SCMP_A0_U32 explícito.

@drakenclimber @jdstrand @michaelweiser, ¿qué piensan ustedes de https://github.com/pcmoore/misc-libseccomp/commit/b9ce39d776ed5a984c7e9e6db3b87463edce82a7 como una solución para esto?

@pcmoore : ¡Gracias por seguir investigando esto! Le di un giro y se ve muy bien en 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) }},

Desafortunadamente, parece que la función auxiliar no es adecuada como inicializador de estructura:

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),
     ^~~~~~~~~~

Gracias por la revisión @michaelweiser , desafortunadamente no pensé que la gente estuviera usando estas macros como inicializadores, pero ese es un uso válido y seguramente hay algunas personas que lo usan de esta manera.

Necesitaré pensar un poco en esto ... ¿Tenías alguna idea sobre cómo resolver esto de una manera elegante?

Ni idea, lo siento, ya tenía los ojos abiertos con fósforos. :)

Mirándolo ahora, creo que veo el problema: debido a las listas de argumentos variables, no puedes inyectar los moldes necesarios, ¿verdad?

¿Podría scmp_arg_cmp quizás contener una unión que brinde diferentes puntos de vista sobre los datos con el ancho correcto, la alineación (y quizás incluso el orden de bytes) (que en mi opinión entra en conflicto con "elegante") :? Si es puramente interno a libseccomp y no necesita ser compatible con una interfaz del kernel, ¿podría llevar un indicador de tipo de datos como un campo separado y hacer que las funciones del usuario lo solucionen? ¿Y eso podría incluso inicializarse usando varargs?

De lo contrario, en lugar de marcar la operación como un 32/64 bit completo, se podrían anotar los operandos, envolviendo una conversión y dando una recomendación severa al usuario (como lo hace) para que siempre use esas anotaciones bajo pena de encontrarse con problemas difíciles 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)) }},

o

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

No soy lo suficientemente un crack de preprocesador para llegar a mucho más, lo siento.

@pcmoore , los cambios me parecen bien. No soy un experto en preprocesadores, pero echaré un vistazo al problema que @michaelweiser mencionó anteriormente.

Mirándolo ahora, creo que veo el problema: debido a las listas de argumentos variables, no puedes inyectar los moldes necesarios, ¿verdad?

Sí, eso es todo. Tal vez haya una forma no terrible de evitar eso, pero aún no he encontrado una.

¿Podría scmp_arg_cmp quizás contener una unión que brinde diferentes puntos de vista sobre los datos con el ancho correcto, la alineación (y quizás incluso el orden de bytes) (que en mi opinión entra en conflicto con "elegante") :? Si es puramente interno a libseccomp y no necesita ser compatible con una interfaz del kernel, ¿podría llevar un indicador de tipo de datos como un campo separado y hacer que las funciones del usuario lo solucionen? ¿Y eso podría incluso inicializarse usando varargs?

Tenemos un problema porque la estructura scmp_arg_cmp es parte de la API de libseccomp, así que a menos que queramos aumentar la versión principal de libseccomp, no podemos cambiar el tamaño de la estructura o el desplazamiento de ninguno de los campos miembros; hacerlo rompería la interfaz binaria existente con las aplicaciones existentes. Convertir los campos de referencia de 64 bits en una unión que contenga un valor de 64 bits o de 32 bits debería estar bien en sí mismo, pero también necesitaría agregar información adicional a la estructura scmp_arg_cmp para indicar cuál miembro de la unión usar ; es esta bandera adicional la que podría ser problemática.

Podría ser posible robar algunos bits de los campos "arg" u "op", ambos son valores de 32 bits y están usando solo una fracción de ese espacio. Sin embargo, considero que es una opción bastante extrema y me gustaría evitarla si es posible.

De lo contrario, en lugar de marcar la operación como un 32/64 bit completo, se podrían anotar los operandos, envolviendo una conversión y dando una recomendación severa al usuario (como lo hace) para que siempre use esas anotaciones bajo pena de encontrarse con problemas difíciles de depurar. ?

No entiendo muy bien lo que obtendríamos al envolver los operandos con una macro, ¿puede elaborar un poco más? Podríamos proporcionar una macro para ajustar los valores de referencia, pero eso no es realmente diferente a simplemente pedir a las personas que llaman que proporcionen la conversión adecuada.

@pcmoore , los cambios me parecen bien. No soy un experto en preprocesadores, pero echaré un vistazo al problema que @michaelweiser mencionó anteriormente.

Muchas gracias. Ojalá entre los tres podamos encontrar algo útil aquí.

@pcmoore : Consultando http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/ y http://efesx.com/2010/08/31/overloading- macros / se me ocurre lo siguiente:

#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 prueba:

        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),
        };

sale de gcc-7.4.0 -E y 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)}),
 };

Suponiendo que SCMP_A[0-5]_43 necesita al menos op para funcionar y SCMP_CMP32 requiere arg , se pueden guardar dos líneas haciendo que esos parámetros sean posicionales:

#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__)

¡Bien hecho @michaelweiser! ¿Quería armar un PR para que podamos revisar / comentar los cambios un poco más fácilmente? Si no, está perfectamente bien, armaré uno y me aseguraré de que obtengas mucho crédito :)

Haré el proyecto de relaciones públicas de esta noche. ¿Encima de https://github.com/pcmoore/misc-libseccomp/commit/b9ce39d776ed5a984c7e9e6db3b87463edce82a7 o desde cero?
¿Cómo le damos crédito al Blogger Roman por su solución de sobrecarga? Encontré lo que parece ser el hogar actual de su blog en https://kecher.net/overloading-macros/. Su publicación parece una fuente bastante original. ¿Comenta con un enlace a la publicación sobre la lógica macro_dispatcher ?

Haré el proyecto de relaciones públicas de esta noche. ¿Encima de pcmoore @ b9ce39d o desde cero?

¡Genial gracias! Continúe y basarlo en la rama maestra, nunca fusioné las cosas en mi árbol misc-libseccomp, y no planeo hacerlo en este punto, ya que su enfoque es mucho mejor.

¿Cómo le damos crédito al Blogger Roman por su solución de sobrecarga? Encontré lo que parece ser el hogar actual de su blog en https://kecher.net/overloading-macros/. Su publicación parece una fuente bastante original. ¿Comenta con un enlace a la publicación sobre la lógica macro_dispatcher ?

Por lo general, no acreditamos a las personas directamente en la fuente, a menos que exista algún requisito de licencia; Recomendaría agregar un comentario en la descripción del parche que acredite a Roman con la idea básica y proporcione un enlace a la publicación de su blog. No veo ninguna licencia o restricción en sus ejemplos, por lo que no creo que haya ningún problema en ese sentido, y según una muestra de su blog, creo que su intención es compartir estas ideas con otros (como nosotros ) para ayudarlos a resolver sus problemas. Si tiene una dirección de correo electrónico para Roman, siempre puede intentar enviarle algún correo electrónico; Si no podemos comunicarnos con él por alguna razón, creo que está bien continuar.

Resuelto a través de 80a987d6f8d0152def07fa90ace6417d56eea741.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

vasanthaganeshk picture vasanthaganeshk  ·  9Comentarios

drakenclimber picture drakenclimber  ·  10Comentarios

kloetzl picture kloetzl  ·  19Comentarios

varqox picture varqox  ·  23Comentarios

srd424 picture srd424  ·  18Comentarios