Indicador Unix O_CREAT sin modo especificado

9 minutos de lectura

Indicador Unix O CREAT sin modo especificado
hiperbóreo

La definición de UNIX abierto() función cuando se usa con el indicador O_CREAT es que requiere un tercer argumento llamado modo para establecer los privilegios de los archivos.

¿Qué pasa si eso modo no esta especificado?

int file;
static const char filename[] = "test.test";

if ((file = open(filename, O_RDWR | O_CREAT | O_TRUNC)) == 1)
{
    perror("Error opening file.");
    exit(EXIT_FAILURE);
}

close(file);

¿Qué sucede con el archivo que se crea usando esas banderas? En mi sistema obtengo:

-r--r-s---  1 hyperboreean hyperboreean     0 2009-02-25 01:40 test.test

Una teoría es que la función abierta busca en la pila y verifica el parámetro de modo y termina usando un número entero aleatorio que encuentra.

¿Qué dice la norma al respecto?

  • Por eso se inventaron los prototipos de funciones.

    – útero

    25 de febrero de 2009 a las 0:04

  • Tu comentario no tiene sentido.

    – Caja de jabón

    25 de febrero de 2009 a las 0:06

  • No lo hace, de hecho. Y tampoco el voto negativo.

    – donner

    25 de febrero de 2009 a las 0:07

  • El término “sobrecargado”, por lo que recuerdo, no existía antes de la llegada de C++. En C, solo puede haber una implementación de una función.

    – donner

    25 de febrero de 2009 a las 0:14

  • @David: el número y los tipos de argumentos de función de open() pueden variar, lo que probablemente no sea el caso aquí, pero no creo que podamos hablar de sobrecarga en C.

    – hiperbóreo

    25 de febrero de 2009 a las 0:17

1647706573 368 Indicador Unix O CREAT sin modo especificado
jonathan leffler

Prototipos del estándar POSIX (IEEE 1003.1:2008) open() como:

int open(const char *path, int oflag, ...);

La sección que describe el comportamiento de O_CREAT no dice qué sucederá si omite el tercer argumento necesario, lo que significa que el comportamiento no está definido: todo es posible.

En la práctica, el uso de una parte de la pila que estaba destinada a ser un marco de pila o una dirección de retorno o algo similar es bastante probable, hasta una aproximación razonable, que puede considerarse un número entero aleatorio.

los POSIX 2008 estándar tiene algunas banderas nuevas e interesantes (y útiles) para open()incluyendo:

  • O_FDCLOEXEC para especificar close-on-exec al abrir.
  • O_DIRECTORY para especificar que el archivo debe ser un directorio.
  • O_NOFOLLOW para especificar no perseguir enlaces simbólicos.

Buena pregunta. los mode valor será modificado por el umask del proceso Así que si no pasas un mode explícitamente a open en un O_CREAT operación, y si esto da como resultado que se utilicen bits aleatorios para el modo, esos bits aleatorios serán modificados por el umask.

Ojalá pudiera ser más definitivo y preciso, pero estoy de acuerdo con cdonner en que se están utilizando valores “aleatorios”, junto con el umask.

Editar: una cosa que podría intentar es usar dtruss o truss o alguna otra instalación para rastrear las llamadas al sistema y ver el valor de mode en tiempo de ejecución para ver si se usa algo sensato, o si son solo bits aleatorios modificados por el umaskpor ejemplo.

  • En mi prueba usando ptrace(), es algo aparentemente aleatorio, aunque en mi caso siempre es un número de 11 bits (12 bits con el 0 inicial para indicar octal) que comienza con 2777.

    – Dan Fego

    4 de marzo de 2009 a las 5:46

  • El valor 02777 es interesante porque implica escritura mundial con el bit set-gid. Bueno, solo otra razón por la que crear un archivo sin el modo es peligroso.

    – Craig S.

    4 de marzo de 2009 a las 18:31

hiperbóreo, su sospecha puede no estar tan lejos de la realidad. Esperaba encontrar la respuesta en Kernighan Ritchie. Desafortunadamente, no lo hice. Creo que se requiere el parámetro de permisos con el indicador O_CREAT, y si no lo proporciona, open() extraerá un valor aleatorio de la pila, que por supuesto pasa desapercibido en C.

Editar: por “aleatorio” quiero decir no predecible. Probablemente esté recogiendo parte de la dirección de retorno, que se encuentra encima de los parámetros en la pila.

Para que conste, en la mayoría de los sistemas libc, probablemente estará en manos de va_argque dice en su página de manual:

   If there is no next argument, or if type is not compatible with the
   type of the actual next argument (as promoted according to the
   default argument promotions), **random errors will occur**.
int
__libc_open64 (const char *file, int oflag, ...)
{
    int mode = 0;

    if (oflag & O_CREAT)
    {
        va_list arg;
        va_start (arg, oflag);
        mode = va_arg (arg, int);
        va_end (arg);
    }

    if (SINGLE_THREAD_P)
        return INLINE_SYSCALL (open, 3, file, oflag | O_LARGEFILE, mode);

    int oldtype = LIBC_CANCEL_ASYNC ();

    int result = INLINE_SYSCALL (open, 3, file, oflag | O_LARGEFILE, mode);

    LIBC_CANCEL_RESET (oldtype);

    return result;
}

Podemos usar macros C para detectar este problema.

#undef open
#define open(a, b, c) open(a, b, c)

Ahora no puedes llamar open sin tres argumentos.

Esto es similar a escribir macros para struct inicializadores, para asegurarse de que los usuarios no se olviden de inicializar algunos miembros:

#define foo_initializer(a, b, c) { .x = (a), .y = (b), .z = (c) }

Si luego agregamos un nuevo miembro wpodemos extender foo_initializer con un nuevo argumento. Cuando recompilamos la base del código, el compilador encontrará todos los lugares donde solo se le dan tres argumentos. Mientras que los inicializadores “desnudos” que no se inicializan w continuará compilando limpiamente.

   int open(const char *pathname, int flags);
   int open(const char *pathname, int flags, mode_t mode);

…abrir()… Indicador O_CREAT…
¿Qué pasa si eso modo no esta especificado?

Además de las respuestas de otras personas, si desea protegerse contra esto en el futuro al obtenertintineo a error del compilador cuando se olvidó de especificar el modo bandera en aquellos casos en que es necesario (es decir. O_CREAR o O_TMPFILEde acuerdo a man 2 open), tendrías que utilizar los Compilador del proyecto GNU C y C++ (por ejemplo, el gcc comando) con arg. -D_FORTIFY_SOURCE=1 (o 2) y una bandera de optimización, por ejemplo. -O1 o lo de siempre -O2 (porque _FORTIFY_SOURCE requires compiling with optimization (-O)).

Por ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int file;
static const char filename[] = "test.test";

if ((file = open(filename, O_RDWR | O_CREAT | O_TRUNC)) == 1)
{
    perror("Error opening file.");
    exit(EXIT_FAILURE);
}

close(file);
}

(guárdelo como archivo: a.c)

$ gcc -D_FORTIFY_SOURCE=2 -O1 a.c
In file included from /usr/include/fcntl.h:328,
                 from a.c:3:
In function ‘open’,
    inlined from ‘main’ at a.c:10:13:
/usr/include/bits/fcntl2.h:50:4: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments
    __open_missing_mode ();
    ^~~~~~~~~~~~~~~~~~~~~~

Entonces obtienes: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments

Los pedantes siguen:
Tenga en cuenta que: -O0 o no -O argumento no funcionará (es decir, no le dirá que olvidó agregar mode porque es como si _FORTIFY_SOURCE no se especificó o simplemente se ignoró):

$ gcc -D_FORTIFY_SOURCE=2 -O0 a.c
In file included from /usr/include/bits/libc-header-start.h:33,
                 from /usr/include/stdio.h:27,
                 from a.c:1:
/usr/include/features.h:382:4: warning: #warning _FORTIFY_SOURCE requires compiling with optimization (-O) [-Wcpp]
 #  warning _FORTIFY_SOURCE requires compiling with optimization (-O)
    ^~~~~~~

Utilizando _FORTIFY_SOURCE te protegerá así para otros casos también y por open() hay un caso mas: open can be called either with 2 or 3 arguments, not morevisto en archivo /usr/include/bits/fcntl2.h como:

__errordecl (__open_too_many_args,
         "open can be called either with 2 or 3 arguments, not more");
__errordecl (__open_missing_mode,
         "open with O_CREAT or O_TMPFILE in second argument needs 3 arguments");

__fortify_function int
open (const char *__path, int __oflag, ...)
{
  if (__va_arg_pack_len () > 1)
    __open_too_many_args ();

  if (__builtin_constant_p (__oflag))
    {
      if (__OPEN_NEEDS_MODE (__oflag) && __va_arg_pack_len () < 1)
    {
      __open_missing_mode ();
      return __open_2 (__path, __oflag);
    }
      return __open_alias (__path, __oflag, __va_arg_pack ());
    }

  if (__va_arg_pack_len () < 1)
    return __open_2 (__path, __oflag);

  return __open_alias (__path, __oflag, __va_arg_pack ());
}

La razón por la que GNU C Compiler (por ej. gcc) es necesario es, al menos, debido al siguiente código del archivo /usr/include/sys/cdefs.h:

#if __GNUC_PREREQ (4,3)
# define __warndecl(name, msg) \
  extern void name (void) __attribute__((__warning__ (msg)))
# define __warnattr(msg) __attribute__((__warning__ (msg)))
# define __errordecl(name, msg) \                                                                                               
  extern void name (void) __attribute__((__error__ (msg)))
#else
# define __warndecl(name, msg) extern void name (void)
# define __warnattr(msg)
# define __errordecl(name, msg) extern void name (void)
#endif       

eso dice que gcc versión 4.3 es el mínimo requerido para que esto funcione. (FYI: mi versión actual es gcc (GCC) 8.3.0)

Así que si lo intentas clang versión 8.0.0 (etiquetas/RELEASE_800/final) Destino: x86_64-pc-linux-gnuno obtienes el error de compilación:

$ clang -D_FORTIFY_SOURCE=2 -O1 a.c

(no hay salida aquí incluso con -, la compilación tuvo éxito: a.fuera creado) porque esta versión clang define __GNUC__ ser 4 y __GNUC_MINOR__ ser 2 por lo tanto, 4.2 está apenas por debajo de los 4.3 necesarios para que funcione; y obligando por ej. 8.3 no funcionará:

$ clang -D_FORTIFY_SOURCE=1 -D__GNUC__=8 -D__GNUC_MINOR__=8 -O1 a.c
In file included from <built-in>:355:
<command line>:2:9: warning: '__GNUC__' macro redefined [-Wmacro-redefined]
#define __GNUC__ 8
        ^
<built-in>:9:9: note: previous definition is here
#define __GNUC__ 4
        ^
In file included from <built-in>:355:
<command line>:3:9: warning: '__GNUC_MINOR__' macro redefined [-Wmacro-redefined]
#define __GNUC_MINOR__ 8
        ^
<built-in>:7:9: note: previous definition is here
#define __GNUC_MINOR__ 2
        ^
2 warnings generated.

Los códigos fuente anteriores provienen de glibc Paquete 2.29.9000.r269.g1f50f2ad85-1 en Arch Linux. es decir.

/usr/include/sys/cdefs.h is owned by glibc 2.29.9000.r269.g1f50f2ad85-1
/usr/include/bits/fcntl2.h is owned by glibc 2.29.9000.r269.g1f50f2ad85-1

PD: sin _FORTIFY_SOURCEpuede obtener modos aleatorios en cada ejecución del programa, como yo hice:

$ ./go
-r-x--s--T 1 user user 0 May 17 17:22 /tmp/broken_perms.log
$ ./go
---sr-s--- 1 user user 0 May 17 17:23 /tmp/broken_perms.log
$ ./go
-rws--x--- 1 user user 0 May 17 17:23 /tmp/broken_perms.log
$ ./go
--wsr-x--T 1 user user 0 May 17 17:23 /tmp/broken_perms.log

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad