2011-01-31 10 views
8

Tengo una aplicación de servidor que se ejecutará bajo una cuenta de sistema porque, en un momento dado, procesará solicitudes en nombre de cualquier usuario en el sistema. Estas solicitudes consisten en instrucciones para manipular el sistema de archivos.¿Cómo se realiza la manipulación de archivos/directorios teniendo en cuenta los privilegios del usuario?

Aquí está el truco: el programa debe tener en cuenta los privilegios de ese usuario en particular al realizar las acciones. Por ejemplo, joe no debería poder modificar /home/larry si sus permisos son 755.

Actualmente mi estrategia es esta

  • que el propietario/grupo del archivo
  • compararlo con el ID de usuario/ID de grupo del usuario que intenta realizar la acción
  • Si bien partido (o si no hay coincidencia), use la parte correspondiente del campo de permiso en el archivo para permitir o denegar la acción

¿Es esto sensato? ¿Hay alguna forma más fácil de hacer esto?

Al principio, estaba pensando en tener varias instancias de la aplicación ejecutándose bajo las cuentas del usuario, pero esta no es una opción porque solo una de las instancias puede escuchar en un puerto TCP dado.

Respuesta

3

Eche un vistazo a samba para ver un ejemplo de esto. El daemon samba se ejecuta como root pero se bifurca y asume las credenciales de un usuario normal tan pronto como sea posible.

Los sistemas Unix tienen dos juegos separados de credenciales: los identificadores de usuario/grupo reales y los identificadores de usuario/grupo efectivos. El conjunto real identifica quién eres en realidad, y el conjunto efectivo define a qué puedes acceder. Puede cambiar el uid/gid efectivo como lo desee si es root — incluyendo a un usuario ordinario y viceversa — ya que los ID de usuario/grupo reales permanecen en la raíz durante la transición. Entonces, una forma alternativa de hacerlo en un solo proceso es usar seteuid/gid para aplicar los permisos de diferentes usuarios hacia adelante y hacia atrás según sea necesario. Si su daemon de servidor se ejecuta como root o tiene CAP_SETUID, esto será permitido.

Sin embargo, observe que si tiene la capacidad de cambiar el uid/gid efectivo a su antojo y su aplicación es subvertida, entonces esa subversión podría volver a poner el uid/gid efectivo en 0 y podría tener una seguridad seria vulnerabilidad. Es por eso que es prudente soltar todos los privilegios de forma permanente tan pronto como sea posible, incluido su usuario real uid/gid.

Por esta razón, es normal y más seguro tener un único socket de escucha ejecutándose como root, luego desviar y cambiar los identificadores de usuario reales y efectivos llamando al setuid. Entonces no puede cambiar de nuevo. Su proceso bifurcado tendría el socket que estaba accept() ed ya que es una bifurcación. Cada proceso simplemente cierra los descriptores de archivos que no necesitan; los sockets permanecen vivos ya que los descriptores de archivo hacen referencia a ellos en los procesos opuestos.

También podría intentar y aplicar los permisos examinándolos individualmente usted mismo, pero espero que sea obvio que esto es potencialmente propenso a errores, tiene muchos casos extremos y más probabilidades de salir mal (por ejemplo, no lo hará). trabajar con POSIX ACL a menos que implemente específicamente eso también).

tanto, usted tiene tres opciones:

  1. tenedor y setgid()/setuid() para el usuario que desea. Si se requiere comunicación, use pipe(2) o socketpair(2) antes de techar.
  2. No fork y seteuid()/setegid() alrededor según sea necesario (menos seguro: más probabilidades de comprometer su servidor por accidente).
  3. No se meta con las credenciales del sistema; hacer la aplicación de permisos de forma manual (menos seguro: más probabilidades de obtener una autorización incorrecta).

Si necesita comunicarse con el daemon, entonces aunque puede ser más difícil hacerlo con un zócalo o un tubo, la primera opción es realmente la manera segura de hacerlo. Ver how ssh does privilege separation, por ejemplo. También puede considerar si puede cambiar su arquitectura, de modo que en lugar de cualquier comunicación, el proceso puede compartir algo de memoria o espacio en disco.

Mencione que considera tener una ejecución de proceso separada para cada usuario, pero necesita un solo puerto TCP de escucha. Aún puedes hacer esto. Solo haga que un daemon maestro escuche en el puerto TCP y envíe las solicitudes a cada daemon de usuario y comuníquese según sea necesario (por ejemplo, a través de sockets de dominio Unix). Esto realmente sería casi lo mismo que tener un daemon maestro de bifurcación; Creo que este último resultaría más fácil de implementar.

Lectura adicional: la página de manual credentials(7). También tenga en cuenta que Linux tiene uid/gids del sistema de archivos; esto es casi lo mismo que uid/gid efectivo a excepción de otras cosas como enviar señales. Si sus usuarios no tienen acceso al shell y no pueden ejecutar código arbitrario, entonces no necesita preocuparse por la diferencia.

+0

¡Gracias por una respuesta muy bien escrita! ¿Sería posible crear un proceso maestro que escuche en un puerto y luego envíe los datos a otro proceso a través de otra conexión de socket? ¿Es eso lo que dices en tu penúltimo párrafo? –

+0

Sí, así es, y tú podrías hacer eso. Sin embargo, basándome en todo lo que dices, estoy bastante seguro de que sería más fácil y más simple para ti implementar un daemon maestro de escucha de bifurcación y hacer que el niño pierda privilegios.El proceso bifurcado tendrá automáticamente acceso al socket de solicitud tal como lo hace el daemon maestro; no hay necesidad de comunicar esa parte. –

+0

Bien. Estoy buscando una solución que también funcione bien con Qt, aunque no es un requisito. Así que lo tomo en consideración a la hora de decidir el enfoque correcto. –

2

Deje que un servidor se ejecute en el puerto del servidor anterior y genere procesos secundarios para los usuarios que inician sesión en el sistema. Los procesos secundarios deben descartar los privilegios y suplantar al usuario que inició sesión. Ahora los hijos ya no pueden hacer daño.

+0

Sí, pero ¿cómo se transfieren las conexiones del servidor a los procesos secundarios? –

+0

Creo que esto funciona al permitir que el proceso raíz configure el socket, y luego simplemente pasa el descriptor de archivo al niño. No sé cómo se hace esto exactamente, pero al menos PostgreSQL funciona de manera similar, donde para cada conexión, se genera un proceso secundario para manejarlo. Y no puedo creer que todo el tráfico deba ser encaminado a través del proceso principal, por lo que pasará algún día por allí. – Daniel

+0

Bueno ... lo investigaré. Sin embargo, esto hace las cosas más complicadas porque los procesos secundarios tienen que comunicarse con el padre. –

3

Haría que mi servidor fork() e inmediatamente setuid(uid) renuncie a los privilegios de root. Entonces cualquier manipulación de archivos sería en nombre del usuario en el que se ha convertido. Como eres hijo del servidor, aún conservas el socket hijo accept() ed que la solicitud (y asumo que responde) continuará. Esto (obviamente) requiere privilegio de root en el daemon.

Pasar descriptores de archivo entre procesos parece innecesariamente complicado en este caso, ya que el niño ya tiene el descriptor de "solicitud".

+0

Hmmm ... eso es ciertamente una posibilidad. –

Cuestiones relacionadas