Passing a password on command line (to a child process started from my program) is known to be insecure (because it can be seen even by other users with ps command). Is it OK to pass it as an environment variable instead?

What else can I use to pass it? (Except of environment variable) the easiest solution seems to use a pipe, but this easiest solution is not easy.

I program in Perl.

  • 2
    Why is it not easy? It doesn't have to be a separate / named pipe, just regular stdin/out will do... that shouldn't be too much trouble in any language. You can put it in a plain config file if you can make sure it's only readable by processes of interest (which is a lot harder than it sounds). – frostschutz Jul 16 '16 at 11:33
  • 1
    If you don't call exec in the child you still have a copy of the password without needing to do anything. – James Youngman Jul 17 '16 at 9:09
up vote 22 down vote accepted

Process arguments are visible to all users, but the environment is only visible to the same user (at least on Linux, and I think on every modern unix variant). So passing a password through an environment variable is safe. If someone can read your environment variables, they can execute processes as you, so it's game over already.

The contents of the environment is at some risk of leaking indirectly, for example if you run ps to investigate something and accidentally copy-paste the result including confidential environment variables in a public place. Another risk is that you pass the environment variable to a program that doesn't need it (including children of the process that needs the password) and that program exposes its environment variables because it didn't expect them to be confidential. How bad these risks of secondary leakage are depends on what the process with the password does (how long does it run? does it run subprocesses?).

It's easier to ensure that the password won't leak accidentally by passing it through a channel that is not designed to be eavesdropped, such as a pipe. This is pretty easy to do on the sending side. For example, if you have the password in a shell variable, you can just do

echo "$password" | theprogram

if theprogram expects the password on its standard input. Note that this is safe because echo is a builtin; it would not be safe with an external command since the argument would be exposed in ps output. Another way to achieve the same effect is with a here document:

theprogram <<EOF
$password
EOF

Some programs that require a password can be told to read it from a specific file descriptor. You can use a file descriptor other than standard input if you need standard input for something else. For example, with gpg:

get-encrypted-data | gpg --passphrase-fd 3 --decrypt … 3<<EOP >decrypted-data
$password
EOP

If the program can't be told to read from a file descriptor but can be told to read from a file, you can tell it to read from a file descriptor by using a file name like `/dev/fd/3.

theprogram --password-from-file=/dev/fd/3 3<<EOF
$password
EOF

In ksh, bash or zsh, you can do this more concisely through process substitution.

theprogram --password-from-file=<(echo "$password")
  • On Solaris 9 and older, /usr/ucb/ps was setuid root so could read and display environment variables of other processes - this was removed in Solaris 10, so the "every other modern Unix variant" answer above applies to Solaris releases from 2005 & later. – alanc Jul 18 '16 at 0:54
  • @alanc Indeed, I don't consider Solaris <10 to be modern these days. Solaris 9 is almost as old as Windows XP! – Gilles Jul 18 '16 at 1:08
  • Gilles: there's days I have a hard time considering Solaris 10 modern now that Solaris 11 has been out more than 5 years, so I completely understand and agree, but sadly know some people still run Solaris 8 or 9. – alanc Jul 18 '16 at 1:52

Instead of passing the password directly through an argument or an environment variable

#!/bin/bash
#filename: passwd_receiver
echo "The password is: $1"

use the same argument or environment variable to pass a filename:

#!/bin/bash
#filename: passwd_receiver
echo "The password is: $(< "$1")"

Then you can pass either a permission-protected regular file (though that won't protect you from other processes running under the same user), or /dev/stdin and pipe it in (which AFAIK will protect you from other processes running under the same user):

 echo PASSWORD | ./passwd_receiver /dev/stdin 

If you use /dev/stdin here, it's imperative that it's a pipe. If it's a terminal, it'll be readable by other processes running under the same user.

If you already need to use your /dev/stdin for something else, you could use process substitution if you're on a shell that supports it, which is essentially equivalent to using pipes:

./passwd_receiver <(echo PASSWORD)

Named pipes (FIFOs) might look like they're the same, but they're interceptable.

These solutions are not perfectly secure either, but they might be close enough provided you're not on a memory constrained system that swaps a lot.

Ideally, you'd read these files (pipe is a file too) into memory marked with mlock(2) as nonswappable, which is what password handling programs such as gnupg generally do.

Notes:

  1. Passing filedescriptor numbers is theoretically just good as file as passing filenames, but filenames are more practical, because <() gives you a filename, not a filedescriptor number (and coprocs give you filedescriptors marked FD_CLOEXEC, which makes those filedescriptors unusable in this context).

  2. If you're on a Linux system where
    /proc/sys/kernel/yama/ptrace_scope is set to 0, then AFAIK, there's no bulletproof way of protecting yourself from other processes running under the same user (they can use ptrace to attach to your process and read your memory)

  3. If you only need to keep your password away from processes running under different (nonroot) users, then arguments, environment variables, pipes and permission protected files will all do.

No, environment variables are easily read too, and leak to child processes. pass it using a pipe.

  • 2
    "environment variables ... leak to child processes" That's the whole point of using an environment variable. They'd be useless if they weren't inherited. "environment variables are easily read too", no they aren't. – Patrick Jul 16 '16 at 17:18
  • 2
    Read the variable, unset it. It's not hard. And you could use the same argument about the pipe. If the pipe isn't read from, it's passed to the child process, and the child process could read it and get the password. – Patrick Jul 16 '16 at 18:53
  • 1
    @Patrick The documented means of unsetting environment variables do not necessarily scrub the value from the location where ps and /proc can see it. – zwol Jul 16 '16 at 19:08
  • 1
    Does some system let you read environment variables of arbitrary processes? I don't think Linux allows it for processes owned by others, and if you are the same user you can just ptrace() the target and read its memory anyway. – ilkkachu Jul 16 '16 at 19:34
  • 5
    This answer is wrong. Environment variables cannot be read by other users. – Gilles Jul 16 '16 at 20:45

If nothing else suits, consider the Linux Key Retention service (kernel keyrings).

Start at: security/keys.txt. One of the default keyrings can be cloned between parent and child processes.

It's not the simplest solution, but it's there and appears to be maintained & used (it was also implicated in an Android bug last year.)

I don't know about its "political" status, but I had a similar need, & started working on a Guile binding. Haven't come across pre-existing Perl support.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.