Skip to content

Commit 1935d54

Browse files
committed
Restrict htoprc symlink resolution with owner check
Restrict the possibility of a symlink attack on the htoprc file. If the htoprc file to be read is a symlink, only resolve the link if it's owned by the same EUID or root user (UID 0). Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
1 parent e9518b3 commit 1935d54

1 file changed

Lines changed: 69 additions & 3 deletions

File tree

Settings.c

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,74 @@ int Settings_write(const Settings* this, bool onCrash) {
799799
return r;
800800
}
801801

802+
static void Settings_resolveSymlink(char** resolvedPath, const char* path) {
803+
int fd = -1;
804+
int openFlags = O_NOCTTY | O_NOFOLLOW | O_NONBLOCK;
805+
#ifdef O_EXEC
806+
openFlags |= O_EXEC;
807+
#else
808+
// O_EXEC is not supported in Linux.
809+
openFlags |= O_RDONLY;
810+
#endif
811+
#ifdef O_PATH
812+
// O_PATH is specific to Linux and FreeBSD.
813+
openFlags |= O_PATH;
814+
#endif
815+
do {
816+
fd = open(path, openFlags);
817+
} while (fd < 0 && errno == EINTR);
818+
819+
if (fd < 0)
820+
goto noPath;
821+
822+
struct stat sb;
823+
int err = fstat(fd, &sb);
824+
if (err)
825+
goto fileBroken;
826+
827+
if (!S_ISLNK(sb.st_mode) || (sb.st_uid != 0 && sb.st_uid != geteuid())) {
828+
// Not a symbolic link or the symbolic link is not trusted.
829+
// Return the path to the link itself. This allows the link
830+
// target to be opened read-only when desirable.
831+
close(fd);
832+
*resolvedPath = xStrdup(path);
833+
return;
834+
}
835+
836+
if (sb.st_size < 0 || sb.st_size >= PATH_MAX)
837+
goto fileBroken;
838+
839+
char buf[PATH_MAX];
840+
ssize_t len = readlinkat(fd, "", buf, PATH_MAX);
841+
close(fd);
842+
843+
if (len < 0 || len > sb.st_size)
844+
goto noPath;
845+
846+
buf[len] = '\0';
847+
size_t dirnameLen = 0;
848+
if (buf[0] != '/') {
849+
const char *lastSlash = strrchr(path, '/');
850+
dirnameLen = lastSlash ? (size_t)(lastSlash - (const char*)path + 1) : 0;
851+
}
852+
char* intermediatePath = xMalloc(dirnameLen + (size_t)len + 1);
853+
memcpy(intermediatePath, path, dirnameLen);
854+
memcpy(intermediatePath + dirnameLen, buf, (size_t)len + 1);
855+
char* ptr = realpath(intermediatePath, buf);
856+
free(intermediatePath);
857+
if (!ptr)
858+
goto noPath;
859+
860+
*resolvedPath = xStrdup(buf);
861+
return;
862+
863+
fileBroken:
864+
close(fd);
865+
noPath:
866+
*resolvedPath = xStrdup("");
867+
return;
868+
}
869+
802870
Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {
803871
Settings* this = xCalloc(1, sizeof(Settings));
804872

@@ -877,9 +945,7 @@ Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable*
877945
legacyDotfile = String_cat(home, "/.htoprc");
878946
}
879947

880-
this->filename = xMalloc(PATH_MAX);
881-
if (!realpath(this->initialFilename, this->filename))
882-
free_and_xStrdup(&this->filename, this->initialFilename);
948+
Settings_resolveSymlink(&this->filename, this->initialFilename);
883949

884950
this->colorScheme = 0;
885951
#ifdef HAVE_GETMOUSE

0 commit comments

Comments
 (0)