October 2009 - Posts

Una práctica que era común en el desarrollo de software de hace unos años consistía en mezclar el código fuente de la aplicación junto con cadenas de texto y otros recursos (iconos, imágenes, etc.). El siguiente diagrama lo explica gráficamente:

Conforme el mercado comenzó a globalizarse fue surgiendo la necesidad de traducir aplicaciones a múltiples idiomas, y por ende este método acabó siendo completamente descartado por los siguientes motivos:

  • Para traducir una aplicación primero hay que crear una o más copias de su código fuente, con el consecuente coste añadido que supone el mantenerlas.
  • Los traductores, que por lo general no tienen conocimientos de programación, tienen que manipular el código fuente para traducir las cadenas de texto. Esto puede introducir errores en el funcionamiento de la aplicación.

Es en esta época cuando el uso de una versión en inglés (o en el idioma nativo de la empresa que desarrollara ese programa) sí podía ofrecer ciertas ventajas con respecto al resto de idiomas. La posibilidad de introducir errores en el código mientras se traducía un programa no era algo descabellado, todo ello debido a que la separación entre código fuente y cadenas de texto dependientes del idioma era insuficiente (en este caso particular, inexistente).

Las versiones de Windows anteriores a Vista introdujeron una nueva arquitectura de localización de binarios, con el objetivo de separar el código fuente (las instrucciones que acaba ejecutando el computador) de las cadenas de texto (mensajes informativos, errores, iconos, etc.).

Si bien esta arquitectura es válida y sigue siendo usada por muchísimas aplicaciones hoy en día, presenta algunas deficiencias que se magnifican en una empresa del tamaño de Microsoft: Por ejemplo, si se detecta un fallo de seguridad en un binario y se quiere desarrollar una actualización de seguridad al respecto, como el código fuente y las cadenas de texto están en el mismo fichero (pese a estar separados entre sí), sería necesario generar un par de decenas o más de actualizaciones de seguridad, una para cada idioma en el que está disponible Windows. Esto retarda innecesariamente el desarrollo de actualizaciones de seguridad en varios idiomas, pues por lo general los errores de seguridad afectan únicamente al código de la aplicación, no a las cadenas de texto y otros recursos que pudiera contener.

Con Windows Vista se rediseñó en profundidad la arquitectura MUI (Multilingual User Interface) que ya estaba presente en la versión Professional de Windows XP (entre otros) para que las cadenas de texto y el código de las aplicaciones estuviera completamente separado, esto es, en ficheros diferentes.

Esta arquitectura MUI introducida con Windows Vista no solo beneficia a Microsoft y al usuario, que ya puede instalar un paquete de idiomas en una versión de Windows que no esté en inglés, también beneficia a los desarrolladores de aplicaciones, pues por fín disponen de una arquitectura proporcionada por el sistema operativo para manejar recursos en múltiples idiomas, sin ser ya necesario que se construyan una desde cero.

¿Qué ocurre al instalar Windows Vista/7?

En un artículo de mi antiguo blog tengo información que explica resumidamente en qué consisten las fases de la instalación de Windows Vista. Básicamente lo que ocurre es que en primer lugar se instalan los binarios del sistema (independientes del idioma), y seguidamente se instala uno o varios paquetes de idiomas. Como ve, el inglés no recibe ningún tipo de trato preferencial, se le trata como un idioma más. La fase correspondiente a la instalación del paquete de idiomas inicial se realiza durante la fase "Instalando características".

En Windows Vista la herramienta encargada de instalar un paquete de idiomas es la denominada Package Manager (Pkgmgr.exe). En Windows 7, se usa la herramienta Dism (Deployment Image Servicing and Imaging), que integra todas las funcionalidades de Package Manager y de otras herramientas igualmente relacionadas con la administración de imágenes de instalación.

En este primer artículo se ha explicado cómo ha ido evolucionando la localización del software a lo largo del tiempo y se ha aclarado que en Windows Vista y Windows 7 los binarios están completamente separados de las cadenas de texto, con las ventajas que esto conlleva. Esto implica también que Vista/7 sean sistemas idependientes del idioma en el que se utilicen; el inglés es un idioma como otro cualquiera.

En un siguiente artículo se explicará en detalle en qué consiste exactamente un paquete de idiomas y qué es lo que conforma la infraestructura MUI de Windows Vista/7. Se explicará también cómo el sistema obtiene los recursos en el idioma establecido por el usuario. Este aspecto puede ser importante para la gente que le interese saber cómo solucionar problemas con Windows, pues me he encontrado con algún que otro sistema cuyo síntoma es que una determinada aplicación no muestra ningún tipo de interfaz gráfica (es decir, no se abre) y cuya causa es una configuración incorrecta de los recursos MUI de ese sistema.

En un foro que frecuento un usuario tenía el siguiente problema: Al intentar ejecutar como administrador un programa en Windows 7 usando el comando Runas.exe, le aparecía el siguiente mensaje de error:

740: La operación solicitada requiere elevación.

Ciertamente el usuario estaba proporcionando el nombre de usuario y la contraseña de un usuario con privilegios administrativos así que... ¿dónde estaría el problema?

En primer lugar, vamos a analizar con calma lo que quiere decir el mensaje de error. El mensaje de error "La operación solicitada requiere elevación" está recogido en el fichero de cabecera Ntstatus.h del SDK de Windows Vista y posteriores. Concretamente está definido con el nombre de macro ERROR_ELEVATION_REQUIRED.

¿Cuándo se devuelve ese código de error durante el funcionamiento de Windows?

En Windows los procesos se crean usando la API CreateProcess. Uno de los pasos que se siguen antes de crear un proceso en Windows Vista y posteriores es comprobar si el proceso en cuestión necesita elevar sus privilegios o no. A la hora de determinar este aspecto, entran en juego, entre otros, dos parámetros: El nivel de ejecución (runlevel) impuesto por el manifiesto de la propia aplicación, y el token del usuario que está lanzando ese programa. Existen tres posibles niveles de ejecución para una aplicación:

  • asInvoker: El nivel de ejecución es el mismo que el del proceso padre.
  • highestAvailable: El nivel de ejecución es el de mayores privilegios posibles, considerando el tipo de cuenta desde la cual se ejecuta el proceso.
  • requireAdministrator: El nivel de ejecución es administrativo, independientemente de las demás circunstancias.

Esta tabla recoge de forma básica las posibilidades:

Nivel de ejecución de la aplicación Token del usuario Requiere elevación o no
AsInvoker Cualquiera No
highestAvailable Administrador "elevado" No
highestAvailable Administrador "estándar"
highestAvailable Otro caso No
requireAdministrator Administrador "elevado" No
requireAdministrator Otro caso

Por administrador "elevado" me refiero a aquella cuenta con privilegios administrativos pero que, o bien ya ha elevado explícitamente sus privilegios (a través de un cuadro de UAC), o bien no tiene activada la característica Modo de aprobación del administrador (Admin Approval Mode). Esta característica se puede desactivar desde Directiva de grupo, pero yo personalmente lo desaconsejo. Un ejemplo de este tipo de cuentas es la cuenta "Administrador", creada durante la instalación del sistema operativo. Recuerde que el resto de cuentas con privilegios administrativos poseen dos tokens: uno limitado, con el que "actúan" por defecto, y otro "administrativo", que pueden aplicar si el usuario así lo indica a través de un cuadro de UAC.

Una vez que CreateProcess falla (devuelve un valor distinto de 1) y el último error se establece como ERROR_ELEVATION_REQUIRED (columna "Requiere elevación o no" de la tabla anterior), es asunto del proceso padre el tratar convenientemente este error. Si no lo hace, se registrará en Visor de sucesos, apartado dedicado a UAC, un evento informando de que el proceso padre no trató apropiadamente el error ERROR_ELEVATION_REQUIRED.

¿Qué ocurre cuando se lanza una aplicación desde la línea de comandos?

Imaginemos que el usuario intenta ejecutar el desfragmentador de disco de Windows 7 usando para ello una sintaxis de Runas.exe como esta:

runas /u:UsuarioAdministrador dfrgui.exe

Básicamente la línea de comandos detecta que no se trata de un comando interno, sino que se trata del programa Runas.exe, y por tanto se dispone a ejecutarlo usando CreateProcess. Como Runas.exe no es un programa que está marcado para requerir elevación de privilegios, CreateProcess retorna éxito.

Una vez que el planificador del sistema operativo cede el control al proceso Runas.exe recién creado, este se dispone a analizar su línea de comandos. Se encuentra con una petición de ejecutar Dfrgui.exe como si fuera el usuario administrador pasado como parámetro. Sin embargo, Runas.exe es poco más que un "envoltorio" de la API CreateProcessWithLogonW, que a su vez es poco más que un envoltorio de la API CreateProcess, pero pasando otro perfil de usuario distinto como parámetro.

Durante su ejecución, CreateProcess determina que Dfrgui.exe requiere privilegios administrativos (puesto que así se lo indica su manifiesto), y por ello devuelve falso y establace el último error como ERROR_ELEVATION_REQUIRED. Como Runas.exe es una herramienta que no está diseñada para tratar apropiadamente ese error, simplemente lo devuelve por pantalla de la forma "740: La operación solicitada requiere elevación".

Una vez desvelado el misterio, resta por comentar un aspecto que a estas alturas quizá ya se haya planteado:

¿Cómo es posible que ejecutando Dfrgui.exe desde la línea de comandos -sin usar Runas.exe- sí que aparezca un cuadro de UAC?

El secreto está en que la línea de comandos, antes de llamar a CreateProcess, informa al sistema de que va a tratar los posibles ERROR_ELEVATION_REQUIRED que puedan surgir. Esto lo consigue estableciendo unos atributos no documentados a la estructura STARTUPINFOEX, que se pasa como parámetro a la familia de API CreateProcess. Esto hace que no se registre el correspondiente error en el apartado UAC del Visor de sucesos. Seguidamente, la línea de comandos, sabiendo que se trata de un programa que debe elevar sus privilegios, lo intenta ejecutar mediante la API ShellExecuteEx. Esta API sí muestra el correspondiente cuadro de UAC, así que es la vía apropiada para ejecutar una aplicación que requiera privilegios administrativos.

En resumen, me gustaría resaltar estos puntos del tema del artículo:

  • En Windows Vista y Windows 7 la única forma de ejecutar algo con privilegios administrativos es mediante un cuadro de UAC. No vale con poner el nombre de usuario y la contraseña de un administrador en la línea de comandos.
  • Todo lo que se ejecute mediante la API CreateProcess, o alguna derivada de ella, no hará que aparezca un cuadro de UAC. Un ejemplo de ello es la utilidad Runas.exe.
  • Runas.exe, en Windows Vista/7, sirve para ejecutar una aplicación como si fuera otro usuario, pero no sirve para ejecutarla con mayores privilegios.
  • Si quiere que se ejecute una aplicación que requiere privilegios administrativos, use la API ShellExecuteEx, o alguna de sus derivadas. Esta API sí permite la aparición de un cuadro de UAC para que el usuario eleve sus privilegios.