Matt O. Matt O. - 2 years ago 84
Perl Question

Perl System Call not Interpretting Variable Correctly

I'm using Perl to pass a string ($password) to a system line to be interpreted. It doesn't seem to be interpreted correctly though, because when I go to attach the Sparse Bundle it says the authentication failed. As a note, everything after the pipe works fine.

$password = chomp($password);

### Create the bash system call to create the sparse bundle with the password
my $cmd = `echo $password | hdiutil create -size 200g -type SPARSEBUNDLE -encryption -stdinpass -volname \"Encrypted Storage for Matt\" -fs \"Case-sensitive Journaled HFS+\" -verbose ~/Desktop/SparseBundle`;

Some sample output:

SLC1087-Matt:backups matt$ ./
DIDiskImageCreatorProbe: interface 1, score 1000, CSparseBundleDiskImage
DIDiskImageCreatorProbe: interface 2, score -1000, CSparseDiskImage
DIDiskImageCreatorProbe: interface 3, score -1000, CRawDiskImage
DIDiskImageCreatorProbe: interface 7, score -1000, CWOUDIFDiskImage
DIDiskImageCreatorProbe: interface 9, score -1000, CCFPlugInDiskImage
DIDiskImageCreateWithCFURL: CSparseBundleDiskImage
CBSDBackingStore::createProbe directory, not a valid image file.
DIBackingStoreCreatorProbe: interface 0, score -1000, CBSDBackingStore
DIBackingStoreCreatorProbe: interface 1, score 1000, CBundleBackingStore
DIBackingStoreCreatorProbe: interface 2, score 0, CRAMBackingStore
DIBackingStoreCreatorProbe: interface 3, score 100, CCarbonBackingStore
DIBackingStoreCreatorProbe: interface 5, score -100, CCURLBackingStore
DIBackingStoreCreateWithCFURL: CBundleBackingStore
DIFileEncodingCreatorProbe: interface 2, score 1000, CEncryptedEncoding
DIFileEncodingCreateWithCFURL: CEncryptedEncoding
DIFileEncodingCreatorProbe: interface 2, score -1000, CEncryptedEncoding
DIBackingStoreCreatorProbe: interface 0, score 100, CBSDBackingStore
DIBackingStoreCreatorProbe: interface 1, score -1000, CBundleBackingStore
DIBackingStoreCreatorProbe: interface 2, score 0, CRAMBackingStore
DIBackingStoreCreatorProbe: interface 3, score 100, CCarbonBackingStore
DIBackingStoreCreatorProbe: interface 5, score -100, CCURLBackingStore
DIBackingStoreCreateWithCFURL: CBSDBackingStore
DIBackingStoreCreateWithCFURL: creator returned 0
DIFileEncodingCreateWithCFURL: creator returned 0
DIBackingStoreCreateWithCFURL: creator returned 0
DIDiskImageCreateWithCFURL: creator returned 0
DI_kextWaitQuiet: about to call IOServiceWaitQuiet...
DI_kextWaitQuiet: IOServiceWaitQuiet took 0.000003 seconds
2016-05-18 20:59:09.627 diskimages-helper[68122:1796245] *** -[NSMachPort handlePortMessage:]: dropping incoming DO message because the connection is invalid
hdiutil: create: returning 0
SLC1087-Matt:backups matt$ hdiutil attach ~/Desktop/SparseBundle.sparsebundle
Enter password to access "SparseBundle.sparsebundle":
hdiutil: attach failed - Authentication error

Everything seems to work fine but the password is apparently wrong.

Answer Source

The hdiutil utility requires a null-terminated password.

That is, "mypass" isn't good enough. It needs to see "mypass\0" because the password is allowed to contain funny characters like embedded newlines.

So, what it's seeing is "mypass\n" instead of "mypass\0"

You could try this as it will work under linux:

echo -n -e "mypass\x00" | hdiutil ...

But ... AFAICT, the OSX version of echo doesn't have -e, so you may need to experiment.

If all else fails, replace the echo with:

echo "mypass" | perl -e '$_ = <STDIN>; chomp ; print $_,"\x00"'

There may be simpler ways to do/express this. You could create a second perl script (call it passme):

print $ARGV[0],"\x00";

The replace the echo with passme. I would put double quotes around the argument.


I'm pretty sure because of the stdinpass that the printf has to be piped to the command. I don't know if I'm exactly right on that.

echo and printf will be piped to the command. But, there are even more ways to do this that I will recommend over those.

As others have mentioned, the cleanest way may be to use IPC::Run3. It's one stop shopping.

But, it may not be installed by default on your system [or other OSX systems]. Dunno--YMMV. You may need to investigate this. So, you might have to install it yourself [from CPAN, I presume(?)]

If your script is only for your own usage, the above may be fine. However, if you're creating the script to be used by others, it may end up on a system that does not have Run3 and the sysadm won't install it.

So, you have to balance ease of use vs ubiquity. That will be your choice.

Run3 sets up both stdin and stdout on the hdiutil command, which is what you need because you want to give it the password and capture its output.

If you only needed one, you could use perl's open function in "pipe" mode. See man perlipc for details. It's pretty simple to use.

Or, you could "roll your own" equivalent of what run3 does by using perl's intrinsic pipe call. You would need two pipes. It's a bit of work, so maybe something for the future.

The biggest problem with the echo, printf, [and passme] approaches is [as others pointed out], is that because they use argv to get the password, they "bleed" it to other processes [briefly].

So, we'd like to have a method that is secure [e.g. Run3 would be]. That's why hdiutil takes a password on stdin instead of a command line argument.

Actually, the simplest way may be to dump the password to a temp file:

use Fcntl;

my $fd;
my $tmp = "/tmp/pwfile";

my $password = "whatever";

# NOTE: by using sysopen and setting the 600 permission atomically, we avoid a
# race condition that may allow another user to open this file
sysopen($fd,$tmp,O_WRONLY | O_CREAT,0600) or
    die("unable to open '$tmp' -- $!\n");
syswrite($fd,$password . "\x00");

my $cmd = `hdiutil ... < $tmp`;

If you don't want to write the password to a temp file, no matter how briefly it may linger around, there is yet another way. It's similar to the passme script approach above, except it passes down the password as an environment variable. This avoids exposure of the password via command line arguments.

Here's the passme variant:


This could also be done with an inline:

perl -e 'print($ENV{"PASSME"},"\x00")'

Then, your script becomes:

my $password = "whatever";

$ENV{"PASSME"} = $password;
my $cmd = `passme | hdiutil ...`;
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download