Search code examples
clinuxpipepopenveracrypt

Piping a passphrase to Veracrypt with popen() - is it secure?


I am trying to make mounting several Veracrypt volumes with the same password more convenient on the Linux commandline. Since Veracrypt does only support passphrase caching in GUI-mode, I wrote the following code to do the job for me:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<time.h>

int main(int argc, char* argv[]){
if(argc > 1 && argc%2==1){
    srand(time(0));

    //prevent veracrypt from asking for user's passphrase
    system("sudo echo -n");

    char *buffer = getpass("Veracrypt Password:");

    for(int i = 1; i<argc; i+=2){
        char* cc;
        cc = (char *) malloc(57+strlen(argv[i])+1+strlen(argv[i+1]));
        strcpy(cc, "veracrypt -t --protect-hidden=no --keyfiles=\"\" --pim=123 ");
        strcat(cc, argv[i]);
        strcat(cc, " ");
        strcat(cc, argv[i+1]);

        FILE* fChild = popen(cc, "w");
        fprintf(fChild, "%s", buffer);
        pclose(fChild);

        free(cc);
    }
    for(int i = 0; i<strlen(buffer); i++)
    buffer[i] = rand();
}
return 0;
}

The code works, but I wonder whether the passphrase is properly deleted from the memory after excecution. As shown in the code above, the passphrase is read into the char-array buffer in the beginning and replaced with random values in the end.

My two questions are:

  • Is this approach a good idea at all? (securitywise)

  • How is the value of buffer piped to veracrypt by popen()? / Is buffer read directly from its location or is it copied and can therefore remain somewhere in memory?


Solution

  • The approach is ok and is as secure as is pipeing the password in the shell <<<"myPassword" veracrypt.

    1. There is no password in the ps output.
    2. The password is stored in temporary buffers only.
    3. I think an attacker could still get the password using some side channel attacks, if it knows enough about your application/source code.

    Your code is not secure at all.

    1. You don't check the return value of malloc
    2. You don't check the return value of popen
    3. You don't check the return value of getpass
    4. You overflow the allocated memory for cc. You didn't allocate place for the terminating null character. You could use asnprintf and let GNU library do the job for you.
    5. Because you don't pass properly the argv[i] and argv[i+1] it's plain simple to attack any PC using your program, with just ex.: ./your_program "; sudo rm -rf <some_file>" "; echo I can run any shell script here".

    Is this approach a good idea at all? (securitywise)

    The approach is ok, how you approached it is not ok. Your program leaks memory and doesn't check any return values and has no control over the strings passed to popen, which is just unsecure. Using system(sudo echo -n) is also insecure. As for the approuch, would be best to bzero the buffer after it's last use memset(buffer, 0, strlen(buffer) + 1) (maybe multiple times, like 5), and then free(buffer).

    In the light of last attacks like Meltdown and Spectre and others, newer ssh versions encrypt the password with a long key (I think with RSA, not sure) right after receiving it from user and decrypt each time upon use. The key is long enough to make attacks using such methods not probable or too long. I don't think there is need for easy small application to implement such method. source.

    How is the value of buffer piped to veracrypt by popen()?

    Because you use fprintf, the buffer is copied into the internal FILE* buffer and then flushed on the newline. By default FILE* streams are buffered and flushed on newline. You can specify the behavior with setvbuf, however, I don't think it's safe at all, as the password will remain in the FILE* buffer for some time. Then the fprintf call writes the content of the internal FILE* buffer upon newline to the associated pipe file descriptor with the FILE* pointer. Then kernel passes the data from the pipe's input to the command's stdin. A little tiny bit safer way (as you don't need printf utility at all, you just "%s"...), is probably to use setvbuf(fChild, NULL, _IONBF, 0) and then to use fwrite(buffer, strlen(buffer), 1, fChild).

    A proper approach would be to remove the FILE* and to use the proper pipe() + fork() + exec() and stream the password directly into the pipe with write() call, so you don't use FILE* internal buffering. fork() will also allow you to send signals and handle the return value of the child.

    Is buffer read directly from its location or is it copied and can therefore remain somewhere in memory?

    Yes and yes and yes. It is read directly from it's location inside fprintf call. It is copied into internal FILE* buffer. It can therefore remain somewhere in memory.