Skip to content

Commit a32f17e

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 affc210 commit a32f17e

1 file changed

Lines changed: 62 additions & 3 deletions

File tree

Settings.c

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,67 @@ 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+
char* intermediatePath = xStrdup(buf);
845+
char* ptr = realpath(intermediatePath, buf);
846+
free(intermediatePath);
847+
if (!ptr)
848+
goto noPath;
849+
850+
*resolvedPath = xStrdup(buf);
851+
return;
852+
853+
fileBroken:
854+
close(fd);
855+
noPath:
856+
*resolvedPath = xStrdup("");
857+
return;
858+
}
859+
799860
Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {
800861
Settings* this = xCalloc(1, sizeof(Settings));
801862

@@ -873,9 +934,7 @@ Settings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable*
873934
legacyDotfile = String_cat(home, "/.htoprc");
874935
}
875936

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

880939
this->colorScheme = 0;
881940
#ifdef HAVE_GETMOUSE

0 commit comments

Comments
 (0)