This is the mail archive of the libc-alpha@sources.redhat.com mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

glibc 2.3.3 ldconfig breaks openssl's libssl.so



[Note: this is a verbose post because I thought some additional history
 and background was in order and might be helpful.]


Recently I upgraded my i586 Linux system from glibc 2.2.5 to 2.3.3 and
in so doing ran into a rather curious problem concerning how the newer
ldconfig operates. This issue will affect people using the shared
libraries from recent versions of openssl. Namely, the 2.3.3 ldconfig
prevents applications from successfully linking (via ld.so) to libssl.so.0
(if these libraries are not in /lib or /usr/lib). A previous post to the
openssl mailing list on a somewhat similar (but not directly related)
issue by Frodo Looijaard back in October 2001 (he was using glibc 2.2.5),
helped motivate me to take a closer look into the matter:

http://www.mail-archive.com/openssl-dev@openssl.org/msg09685.html


Openssl installs its libssl.so files like (version 0.9.7d shown
and ls command output edited for brevity):


[mshell]: ls -la /usr/local/ssl/lib/libssl.so*
/usr/local/ssl/lib/libssl.so -> libssl.so.0.9.7
/usr/local/ssl/lib/libssl.so.0 -> libssl.so.0.9.7
/usr/local/ssl/lib/libssl.so.0.9.7


Users may sometimes add an additional link like:

/usr/local/ssl/lib/libssl.so.0.9 -> libssl.so.0.9.7

libssl is somewhat unusual in that the full version number is carried
within the soname. Thus, the libssl.so.0.9.7 file uses an internal DSO
name (DT_SONAME) string of "libssl.so.0.9.7" NOT "libssl.so.0" as is
typically done with other .so libraries. The openssl developers might have
done this because they consider the shared version of the openssl library
to be experimental. Personally, I think it is a bad idea because API's
should never change within the same major version number.

(BTW, if anyone knows a tool I can use to quickly see what string is
used for the DT_SONAME of an ELF .so binary, do let me know. As it is,
I have to use elfdump to find the string table and index offsets and
then find things using a hex editor. Yuck!)

Most client applications using the dynamic versions of libssl link 
against libssl.so.0. For example, my email client application
"Sylpheed" does this.

I had thought it a good idea to retain the old glibc 2.2.5 ldconfig as
"ldconfig_225" and I am now glad that I had done so:

ldconfig_255 --version
ldconfig (GNU libc) 2.2.5

while my new ldconfig is:

ldconfig --version
ldconfig (GNU libc) 2.3.3


My /etc/ld.so.conf has the line (among others):

/usr/local/ssl/lib


Now, with this setup, running the older ldconfig yields:


[root]: ldconfig_225 -v | grep ssl
/usr/local/ssl/lib:
        libssl.so.0.9.7 -> libssl.so.0.9.7


Then, checking the ld.so.cache for libssl, we find:

[root]: ldconfig_225 -p | grep libssl
        libssl.so.0.9.7 (libc6) => /usr/local/ssl/lib/libssl.so.0.9.7
        libssl.so.0.9 (libc6) => /usr/local/ssl/lib/libssl.so.0.9
        libssl.so.0 (libc6) => /usr/local/ssl/lib/libssl.so.0
        libssl.so (libc6) => /usr/local/ssl/lib/libssl.so

OK, good.

However, using the new glibc 2.3.3 ldconfig:


[root]: ldconfig -v | grep ssl
/usr/local/ssl/lib:
        libssl.so.0.9.7 -> libssl.so.0.9.7


But, checking the ld.so.cache:


[root]: ldconfig -p | grep libssl
        libssl.so.0.9.7 (libc6) => /usr/local/ssl/lib/libssl.so.0.9.7
        libssl.so (libc6) => /usr/local/ssl/lib/libssl.so


Oh no! What happened to libssl.so.0 and libssl.so.0.9 !?

Note to those others in a jam: one workaround is to add symlinks
in a different directory:


[root]: ln -s /usr/local/ssl/lib/libssl.so.0.9.7 /usr/lib/libssl.so.0


and ld.so will then find libssl.so.0 (even without running ldconfig
because /usr/lib is scanned even without ld.so.cache). But, this is
a kludge at best.

Digging into the source code, I find that the problem was introduced
by code that resulted from a patch first proposed by H.J. Lu on July
21, 2003:

http://sources.redhat.com/ml/libc-alpha/2003-07/msg00098.html

So, only glibc 2.3.3 (and later if we don't fix it) are affected.

What Lu is trying to do is to say that under this circumstance:


[mshell]: ls -la libfoo/lib*
libfoo/libfoo-2.1.1.so
libfoo/libfoo-2.1.3.so -> libfoo_subdir/libfoo-2.1.3.so

libfoo/libfoo_subdir:
libfoo-2.1.3.so


ldconfig should link to the libfoo version 2.1.3 not 2.1.1 as is
done under versions of ldconfig in glibc 2.3.2 and prior:


[mshell]: ldconfig_225 -nv libfoo
libfoo:
        libfoo.so.2 -> libfoo-2.1.1.so (changed)

[mshell]: ldconfig -nv libfoo
libfoo:
        libfoo.so.2 -> libfoo-2.1.3.so (changed)


Now, first of all we must figure out what it is we are trying to do and
whether or not it is the "right thing". Then we can develop a correct
algorithm to carry it out. This can become surprisingly confusing for
something that at first sight seems almost trivial.


For the ldconfig man page states:

   "... ldconfig  checks the header and file names of the libraries
        it encounters when determining which versions should have their
        links updated. ldconfig ignores symbolic links when scanning
        for libraries. "


My interpretation of this is that ldconfig should never treat symlinks as
files. Simple as that. If we want to change this, it is important that
the documentation and usage notes be properly updated to reflect the new
behavior. (GNU's apparent dislike of man pages which are beloved by 
Unix sysadmins everywhere often strikes a raw nerve with me.) It is also
important to understand and articulate carefully what we want to do and
why.

If the ldconfig documentation and my interpretation of it is considered
correct and the right thing to do, then the relevant changes in 2.3.3
IMHO need to be rolled back and no further action need be considered.
(Sorry, Lu. ;) But, if what Lu wants to do is legit, then the algorithm
needs further review.

Lu's code was as follows:


          /* If the path the link points to isn't its soname, we treat
             it as a normal file.  */
          if (strcmp (basename (real_name), soname) != 0)
            is_link = 0;
          else
            {
              free (soname);
              soname = xstrdup (direntry->d_name);
            }


What this code does is to treat as files all links whose basenames 
(the leading path to the symlink is stripped off, e.g., \usr\lib\lib.so
becomes lib.so) are not the same as the soname of the DSO object they
point to. Despite what the code comments say, my tests indicate that
real_name (and real_file_name for that matter) is the filename of the
symlink, not what it points to. The real_ variables seem to have to
do with the use of the chroot option rather than what a symlink
resolves to. Perhaps someone more knowledgeable than I can shed some
light on the matter.


At any rate, on August 24, 2003 Jakub Jelinek noticed that Lu's patch
would result in the creation of additional unwanted symlinks with regard
to existing symlinks that are intended for the linker ld(1):

http://sources.redhat.com/ml/libc-alpha/2003-08/msg00120.html

Jelinek wrote:


  "Unfortunately, this patch creates unwanted symlinks in /usr/lib.
   On typical glibc installation, one has
   /lib/librt-2.3.2.so
   /lib/librt.so.6 -> librt-2.3.2.so
   /usr/lib/librt.so -> ../../lib/librt.so.6
   With this patch, another symlink is created:
   /usr/lib/librt.so.6 -> librt.so

   This means there is one more (unneeded) entry in ld.so.cache, and if
   arch subdirs below /lib are used, it means things break if
   LD_LIBRARY_PATH is used."


Jelinek suggested a new algorithm which Ulrich Drepper applied after
some minor adjustments. This revised code eventually became a part of
the glibc 2.3.3 release and remains in current CVS today:


      /* A link may just point to itself.  */
      if (is_link)
        {
          /* If the path the link points to isn't its soname and it is not
             .so symlink for ld(1) only, we treat it as a normal file.  */
          const char *real_base_name = basename (real_file_name);

          if (strcmp (real_base_name, soname) != 0)
            {
              len = strlen (real_base_name);
              if (len < strlen (".so")
                  || strcmp (real_base_name + len - strlen (".so"), ".so") != 0
                  || strncmp (real_base_name, soname, len) != 0)
                is_link = 0;
            }
        }


Jelinek's improved algorithm basically says:

If the basename of the symlink is not the soname of the object pointed to
AND either or both of the following is true:

  a. The basename does not end in ".so"
     OR
  b. The basename is not the first part of the soname.
  
THEN the symlink will be treated as a file.

The purpose of the additional two ORs is to ensure that if the filename
ends in ".so" AND the basename is the first part of the soname (these two
conditions together are properties of links for ld(1)), then the link
should NOT be treated as a file.

Unfortunately, this breaks

libssl.so.0 -> libssl.so.0.9.7
and
libssl.so.0.9 -> libssl.so.0.9.7

because they do not end in ".so" and the libssl soname is "libssl.so.0.9.7"
which is not the same thing as "libssl.so.0" or "libssl.so.0.9".

If my understanding is correct, then what is desired is:

If the basename of the symlink is not the first part of (or the same as)
the soname AND the basename ends in ".so" THEN the symlink will be treated
as a file.

The following code does this:

------


      /* treat certain links as files */
      if (is_link)
         {
         /* If the link name is not the first part of, or the same as, 
            the soname and the link name ends in ".so", treat the link as a file. */
         const char *real_base_name = basename (real_file_name);
         len = strlen (real_base_name);
         if (strncmp (real_base_name, soname, len) != 0)
            { /* Although the real_base_name is of the form libX.soY, we do a sanity
                 check to ensure there are never any out-of-bounds array accesses */
            if (len > strlen(".so"))
               {
               if (strcmp (real_base_name + len - strlen (".so"), ".so") == 0)
               is_link = 0;
               }
            }
         }

      if (real_name != real_file_name)
         free (real_name);

      /* links just point to themselves, so soname is freed */
      if (is_link)
         {
         free (soname);
-----

Which I think will make Lu, Jelinek and myself happy campers - if it is
legit to follow these symlinks in the first place! I also think the
added comment "links just point to themselves, so soname is freed" clarifies
things better than the existing comment "A link may just point to itself."
which should not appear above the treat-links-as-files code as it currently
does. (So, one comment should be deleted and another one added to the
existing code.)


I have not attached a patch because of the need for further thought and
discussion on this matter.



  Sincerely,

  Mike Shell
  news1@michaelshell.org


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]