With the attached proof of concept I can cause a crash, but is also proves that the returned path can change after a second allocation.

It can only happen when someone reuses the allocated buffer and assumes that strlen(x) gives you the right length for the buffer, however it is quite odd to not make such an assumption.

In any case, it also proves that the returned string can become invalid after another allocation; the result of the proof of concept for me is:
24: /tmp/abcdef/.local/share
25: /tmp/abcdef/.local/share!
24: /tmp/abcdef/.local/games
<and a crash for invalid free>

This shows that after the second allocation the value at the first pointer has changed. Its length has been increased by 1. This is also what is most troublesome to me; it will create transient issues with applications that use these functions where they can't find the data or config folder in subsequent runs because each time it can return a different path due to the "corruption". However, it would be badly debuggable as it only happens with certain lengths of user names.

Be aware that there is some code that changes the environment in the proof of concept that is used to trigger it. On my x86_64 system the current one triggers it; on other systems it might be different. It all depends on the alignment of malloc allocations.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <basedir.h>

int main(int argc, char *argv[]) 
{
	/* Home needs to be a specific length. In my case the total returned string
	 * length, including /tmp/abcdef/.local/share needs to be 24. It might be
	 * different on different architectures. */
	setenv("HOME", "/tmp/abcdef", 1);

	/* Allocate the first string, and print the first. */
	const char *xdg_data_home = xdgDataHome(NULL);
	printf("%d: %s\n", strlen(xdg_data_home), xdg_data_home);
	
	/* Allocate the second string. */
	const char *xdg_config_home = xdgConfigHome(NULL);
	
	/* Now print the first string again; for me it's character byte longer now. 
	 * This also means that we do not place it in the data home directory, but
	 * yet another directory. Possibly with a random name, making it quite
	 * unreliable where the files will be. This will eventually cause all kinds
	 * of transient "file cannot be found" errors. But only for those that have
	 * exactly the "wrong" amount of characters in the $HOME path. */
	printf("%d: %s\n", strlen(xdg_data_home), xdg_data_home);
	
	/* 
	 * Now we really start to mess with things...
	 * We know, from the code, not the documentation, that we have control over
	 * the data, i.e. the data that was allocated is now ours. For strings it
	 * would generally be safe to assume that all characters of the string and
	 * the '\0' belong to the allocated string. 
	 * Imagine we want the share replaced with games to match the games folder
	 * in /usr/.
	 */
	char *path = strrchr(xdg_data_home, '/');
	if (path != NULL && strlen(path + 1) >= 5) {
		strcpy(path + 1, "games");
		printf("%d: %s\n", strlen(xdg_data_home), xdg_data_home);
	}
	
	/* Now clean everything up. */
	free((char*)xdg_data_home); // BOOM
	free((char*)xdg_config_home);
	
	return 0;
}

Reply via email to