Compare commits

...

3 Commits

Author SHA1 Message Date
Xavier Roche
81a27e713f st_cookies: assert the saved jar is non-empty
The mode assertions alone would pass a cookie_save that creates an
empty 0600 file and returns 0; check st_size on both saves (proven by
a negative control that skips the write loop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-10 06:48:22 +02:00
Xavier Roche
a4b05a3f42 CodeQL: exclude cpp/world-writable-file-creation
The rule flags every file-creating fopen (0666 & ~umask): 53 baseline
alerts over mirror/cache/log/test output where umask-controlled modes
are the intended, conventional behavior. Its one real catch, the
cookies jar, is now kept 0600 explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-09 20:16:04 +02:00
Xavier Roche
d6fcf51a57 Keep cookies.txt owner-only (0600)
cookie_save() wrote the jar with fopen, so live session cookies ended
up world-readable under the usual umask. Create it O_CREAT 0600 on
Unix (new HTS_PROTECT_FILE), fchmod pre-existing jars down to 0600 on
rewrite, and close the fd if fdopen fails. The st_cookies selftest
asserts both the fresh-create and the tighten-on-rewrite mode
(ASan-independent, proven by reverting each fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-09 20:16:04 +02:00
4 changed files with 42 additions and 4 deletions

View File

@@ -43,6 +43,12 @@ jobs:
languages: c-cpp
build-mode: manual
queries: security-extended
# fopen's umask-controlled 0666 is intended for mirror/cache/log
# output; the one credential file (cookies.txt) is kept 0600 on Unix.
config: |
query-filters:
- exclude:
id: cpp/world-writable-file-creation
# Manual build: CodeQL traces the compiler, so build exactly what ships.
- name: Build

View File

@@ -307,14 +307,25 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
return -1;
}
// écrire cookies.txt
// !=0 : erreur
/* Write cookies.txt; returns 0 on success. The jar holds live session
cookies, so keep it owner-only on Unix (Windows inherits folder ACLs). */
int cookie_save(t_cookie * cookie, const char *name) {
char catbuff[CATBUFF_SIZE];
if (strnotempty(cookie->data)) {
char BIGSTK line[8192];
#ifdef _WIN32
FILE *fp = fopen(fconv(catbuff, sizeof(catbuff), name), "wb");
#else
const int fd = open(fconv(catbuff, sizeof(catbuff), name),
O_WRONLY | O_CREAT | O_TRUNC, HTS_PROTECT_FILE);
FILE *fp = fd != -1 ? fdopen(fd, "wb") : NULL;
if (fd != -1 && fp == NULL)
close(fd); /* fdopen failed: don't leak the descriptor */
if (fp != NULL) /* O_CREAT's mode skips pre-existing jars: tighten those */
(void) fchmod(fd, HTS_PROTECT_FILE);
#endif
if (fp) {
char *a = cookie->data;

View File

@@ -422,9 +422,10 @@ typedef int T_SOC;
#endif
/* Permission bits for created folders and files (mkdir and chmod).
PROTECT_FOLDER is owner-only. With HTS_ACCESS set (the default) the ACCESS_
modes also grant group/other read; otherwise they stay owner-only. */
PROTECT_FOLDER/FILE are owner-only. With HTS_ACCESS set (the default) the
ACCESS_ modes also grant group/other read; otherwise they stay owner-only. */
#define HTS_PROTECT_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR)
#define HTS_PROTECT_FILE (S_IRUSR | S_IWUSR)
#if HTS_ACCESS
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

View File

@@ -1551,6 +1551,26 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
err = 1;
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
err = 1;
#ifndef _WIN32
/* the jar holds live session cookies: cookie_save must keep it 0600 */
{
const char *jar = "st-cookies-jar.txt";
struct stat st;
(void) UNLINK(jar);
assertf(cookie_save(&cookie, jar) == 0);
assertf(stat(jar, &st) == 0);
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
assertf(st.st_size > 0); /* mode-only checks would pass an empty jar */
/* a pre-existing world-readable jar must be tightened, not kept */
assertf(chmod(jar, 0644) == 0);
assertf(cookie_save(&cookie, jar) == 0);
assertf(stat(jar, &st) == 0);
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
assertf(st.st_size > 0);
(void) UNLINK(jar);
}
#endif
printf("cookie-header: %s\n", err ? "FAIL" : "OK");
if (err)
printf(" got: %s\n", hdr);