A little over a month ago, I discovered an issue with the way radmind 1.5.1 created symlinks under Tiger. This exhibited itself as a broken symlink to a directory on an automounted NFS volume. After discussing the matter with the radmind developers, and being unable to demonstrate any other actual problems caused by this, I decided to script a fix to the broken symlink and move on.
Yesterday, James Reynolds posted to both the MacEnterprise list and the radmind-users list about a problem with Adobe Bridge crashing on launch. James had used ktrace to discover it was crashing when trying to read a symlink.
James also discovered that if he copied the offending files from another (good) machine, Bridge worked as expected. It was only when radmind installed the files that things broke.
This rang a bell. I was quickly able to duplicate James’ issue by using radmind 1.5.1 to install Adobe Photoshop CS2 on a Mac.
After recreating the symlinks like so:
grep -h "^l" /var/radmind/client/AdobePhotoshopCS2.T | /usr/local/bin/lapply -u 022
Adobe Bridge worked as expected.
What is going on here?
There is a bug in Tiger that radmind 1.5.1 is exercising. Classically, owner, group and mode should be ignored on symlinks, deferring to the owner, group and mode of the symlink’s target. But in some operations, Tiger seems to honor symlink permissions, and if they are set so that a user cannot read the link, access to the link’s target will fail.
Here is a demonstration of the bug outside of the context of radmind:
(istanbul.fas) root [210] % uname -a
Darwin istanbul.fas.fa.disney.com 8.5.0 Darwin Kernel Version 8.5.0: Sun Jan 22 10:38:46 PST 2006; root:xnu-792.6.61.obj~1/RELEASE_PPC Power Macintosh powerpc
(istanbul.fas) root [211] % cd /
(istanbul.fas) root [212] % umask 077
(istanbul.fas) root [213] % ln -s /Users/Shared /Shared
(istanbul.fas) root [214] % ls -al /Shared
lrwx------ 1 root admin 13 Mar 2 10:33 /Shared@ -> /Users/Shared
Note the restricted permissons for the /Shared symlink.
(Nothing is returned)
(istanbul.fas) root [215] % umask 022
(istanbul.fas) root [216] % readlink /Shared
/Shared
(istanbul.fas) root [217] % exit
exit
(istanbul.fas) gneagle [202] % readlink /Shared
As root, “readlink /Shared” returns the target path: “/Users/Shared”.
As “gneagle”, a non-privileged user, it returns nothing.
radmind 1.5.1 changed the way it creates files in an attempt to improve security. It changes the umask to 077, creates the file, and then chmods it to its intended permissions. But chmod has no effect on symlinks, and radmind captures no user, group or mode info for symlinks since the are not supposed to be significant.
The net effect is that all sysmlinks are created with owner as root and with mode 700. Normally that would not be a problem, and indeed is not under Panther. But under Tiger, it can be a problem.
So until Apple fixes the bug (I’ve reported to them) or the radmind team implements a workaround in their code, you can avoid this issue going foward by calling lapply with a “-u 022” option. This tells lapply to use a umask of 022 when creating temporary files. The net effect is that symlinks are created with mode 755, which makes them usable by non-root users.
That still leaves a major problem to solve. How do you fix the symlinks already created by radmind with a umask of 077? I developed a script to do just that.
#!/usr/bin/perl –w
# fixSymlinks.pl
# by Greg Neagle, greg.neagle@disney.com
# - This script attempts to fix broken symlinks created by
# lapply 1.5.1 when run with the default umask option
#
# - No attempt is made to fix symlinks referenced in negative
# transcripts
#
# - Symlinks that are referenced in a positive transcript and a
# higher-precedence negative transcript therefore _will_ be
# modified, this could be bad. In my loadsets, I can find no
# actual occurrences of this problem, since the only symlink I
# have in any negative transcript is this one:
# l /private/etc/localtime /usr/share/zoneinfo/US/Pacific
# and it doesn't appear in any other transcripts.
# But the danger is there.
#
# - A deeper potential problem is transcript entries that
# _remove_ symlinks: if a transcript creates a symlink, and
# a higher-precedence transcript removes it, we'll have an
# unwanted symlink hanging around. A subsequent radmind
# session will remove it, but if this script is run again,
# it will come back.
#
# - Therefore we need to deal with "- l" lines in the
# transcripts as well. Unfortunately, we cannot just blindly
# pass these lines to lapply, because if symlink doesn't exist
# in the filesystem, lapply will fail and stop processing
# transcript lines.
#
# So we'll need to feed symlink removal lines to lapply
# one-by-one and ignore failures.
use strict;
my $radmindClientDir = "/var/radmind/client";
my $commandFile = $radmindClientDir . "/command.K";
my $symlinkremovet = "/tmp/symlinkremove.T";
#turn off output buffering
$|++;
(open K, $commandFile) or die "Can't open the command file!";
while (<K>) {
my $commandFileLine = $_;
if ($commandFileLine =~ /^p/) {
#positive transcript
my @lineItems = split;
my $transcript = $lineItems[1];
print "Processing $transcript...\n";
# re-create all symlinks in the transcript:
# grep out all lines that begin with l and send them
# to lapply with a 022 umask
system "/usr/bin/grep -h \"^l\" \"$radmindClientDir/$transcript\" | /usr/local/bin/lapply -u 022";
# grab symlink removal lines
system "/usr/bin/grep -h \"^- l\" \"$radmindClientDir/$transcript\" > $symlinkremovet";
print "Removing symlinks flagged for removal in $transcript...\n";
if (open T, $symlinkremovet) {
while (<T>) {
#feed each line one-by-one to lapply
chomp;
system "echo $_ | /usr/local/bin/lapply -u 022";
}
close T;
}
}
}
close K;
Hi,
there is a similar problem with SuperDuper stopping disk backup upon broken symlinks. Could you imagine to adopt your script such that it removes ALL broken symlinks, not only those produced by Radmind ?
Best
Uwe