#1104659 xscreensaver-settings not resolving symlinked ~/.xscreensaver

Package:
xscreensaver
Source:
xscreensaver
Description:
Screensaver daemon and frontend for X11
Submitter:
Jim Wisniewski
Date:
2025-05-04 04:51:02 UTC
Severity:
normal
#1104659#5
Date:
2025-05-04 04:49:21 UTC
From:
To:
Dear Maintainer,

I have found that if ~/.xscreensaver is a symbolic link to another file, running
xscreensaver-settings and changing one or more settings before exiting will
cause it to remove the symlink entirely and write a new, regular (non-symlink)
~/.xscreensaver file, rather than resolving the symlink and writing to its
target as one would expect.

Steps to reproduce:
1. Create any valid .xscreensaver file, as by invoking xscreensaver-settings
2. `mv .xscreensaver xscreensaver-target`
3. `ln -s xscreensaver-target .xscreensaver`
4. Run xscreensaver-settings, change a setting (e.g. increase the "Blank After"
   time by 1 minute), and close the application

Expected behavior:
- .xscreensaver is still a symlink to xscreensaver-target
- xscreensaver-target has been updated with the new settings

Observed behavior:
- .xscreensaver is now a regular file with the new settings
- xscreensaver-target is unchanged

The strange thing is that the xscreensaver code purports to already handle this
case, via the chase_symlinks function in driver/prefsw.c, and none of the Debian
patches for this package affect that part of the code.  Additionally, I have
locally built the Debian version of 6.06 with all patches applied, and running
that version of xscreensaver-settings works as expected -- the new settings are
written to the symlink target properly.  Therefore I conclude this is not a bug
in the upstream code itself, but in the Debian-provided binary.

The portions of driver/prefsw.c that appear to be relevant here are:

    /* don't use realpath() on fedora system */
    #ifdef _FORTIFY_SOURCE
    # undef HAVE_REALPATH
    #endif

and

    static char *
    chase_symlinks (const char *file)
    {
    # ifdef HAVE_REALPATH
      if (file)
        {
    # ifndef PATH_MAX
    #  ifdef MAXPATHLEN
    #   define PATH_MAX MAXPATHLEN
    #  else
    #   define PATH_MAX 2048
    #  endif
    # endif
          char buf[PATH_MAX];
          if (realpath (file, buf))
            return strdup (buf);

    /*      sprintf (buf, "%.100s: realpath %.200s", blurb(), file);
          perror(buf);*/
        }
    # endif /* HAVE_REALPATH */
      return 0;
    }

I hazard a guess that HAVE_REALPATH is not being set by the configure script, or
possibly _FORTIFY_SOURCE is being set somewhere, as either of those cases would
cause the observed behavior.

Additional supporting observations:

- `objdump -T /usr/bin/xscreensaver-settings` does not mention realpath(3) at
  all, nor does the 6.09+dfsg1-1 binary from testing, implying that it was
  ifdef'd out at compile time.  In contrast, my (working) locally-built binary
  does reference it:

      $ objdump -T driver/xscreensaver-settings | grep realpath
      0000000000000000      DF *UND*  0000000000000000 (GLIBC_2.3)  realpath

- `strace /usr/bin/xscreensaver-settings` shows no sign of readlink(2) being
  called when performing the reproducer steps above, whereas my locally-built
  binary does:

      $ strace driver/xscreensaver-settings 2>&1 | grep readlink
      readlink("/home", 0x7ffecdde1c90, 1023) = -1 EINVAL (Invalid argument)
      readlink("/home/wisnij", 0x7ffecdde1c90, 1023) = -1 EINVAL (Invalid argument)
      readlink("/home/wisnij/.xscreensaver", "code/dotfiles/linux/.xscreensave"..., 1023) = 33
      [etc...]