Skip to content

Commit ee7832c

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 b7f9df9 commit ee7832c

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
@@ -796,6 +796,74 @@ int Settings_write(const Settings* this, bool onCrash) {
796796
return r;
797797
}
798798

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

@@ -873,9 +941,7 @@ Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable*
873941
legacyDotfile = String_cat(home, "/.htoprc");
874942
}
875943

876-
this->filename = xMalloc(PATH_MAX);
877-
if (!realpath(this->initialFilename, this->filename))
878-
free_and_xStrdup(&this->filename, this->initialFilename);
944+
Settings_resolveSymlink(&this->filename, this->initialFilename);
879945

880946
this->colorScheme = 0;
881947
#ifdef HAVE_GETMOUSE

0 commit comments

Comments
 (0)