7

I'm using the latest PHP packages available from https://launchpad.net/~ondrej/+archive/ubuntu/php .

When I build and install the OCI8 extension, everything appears to be in order, but despite enabling the extension in the PHP-FPM configuration, its presence is not reflected in the output from phpinfo().

The following Gist details the exact process that I'm using to configure, build, and install the OCI8 PHP extension:

https://gist.github.com/cbj4074/fa761f60b6f8db431539d76ebfba828e

The very same process and configuration work perfectly well on Ubuntu 16.04 LTS, so it seems that there is some fundamental difference on Ubuntu 18.04 LTS, whether with the operating system or the PHP packages in question.

As a bit of important (and I suspect relevant to this issue) background information, on Ubuntu 18.04 LTS, the extension fails to load in the CLI environment out-of-the-box, with the error:

PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php/20160303/oci8.so' - libmql1.so: cannot open shared object file: No such file or directory in Unknown on line 0

I resolved the issue like so:

# echo 'LD_LIBRARY_PATH="/opt/oracle/instantclient_12_2"' >> /etc/environment

I thought that perhaps adding the LD_LIBRARY_PATH to the PHP-FPM environment configuration might resolve the equivalent issue there:

# echo "env['LD_LIBRARY_PATH'] = /opt/oracle/instantclient_12_2" >> /etc/php/7.2/fpm/pool.d/www.conf
# systemctl restart php7.2-fpm

This does indeed cause the LD_LIBRARY_PATH value, as specified, to be reflected in both the Environment section of phpinfo() (when rendered via PHP-FPM + NGINX and requested from a browser) and the PHP Variables section, as $_SERVER['LD_LIBRARY_PATH'].

Oddly, even with PHP-FPM's logging set to debug, I don't see any trace of the libmql1.so error that I experience with the CLI. The OCI8 extension simply fails to load, silently. display_startup_errors = On in PHP-FPM's effective php.ini, too.

I elected to see if the OCI8 extension works in Apache, on the same server, and it does, provided I add export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 to /etc/apache2/envvars; in its absense, Apache complains on startup:

PHP Warning: PHP Startup: Unable to load dynamic library 'oci8.so' (tried: /usr/lib/php/20170718/oci8.so (libmql1.so: cannot open shared object file: No such file or directory), /usr/lib/php/20170718/oci8.so.so (/usr/lib/php/20170718/oci8.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0

None of this business with the LD_LIBRARY_PATH is necessary on Ubuntu 16.04 LTS, and based on my observations herein and the comments regarding https://stackoverflow.com/a/45242468/1772379 , that changed in Ubuntu 17.10 and Ubuntu 18.04 LTS.

Has anybody else tried this, on Ubuntu 18.04 LTS, specifically?

I've tried this on two different Vagrant VMs, laravel/homestead box 6.0.0, and ubuntu/bionic64 box v20180509.0.0, and the behavior is the same in both.

Any other ideas would be most appreciated!

EDIT 1:

I asked about this issue on the package maintainer's GitHub tracker and he suggested that the problem stems from failing to set an appropriate RPATH at compile time.

I explain in my reply that I am setting an appropriate value, but the issue remains closed.

I do notice an interesting detail, however, which is that the compiled extension on Ubuntu 18.04 uses RUNPATH (and not RPATH, which is used in Ubuntu 16.04). If PHP-FPM ignores RUNPATH, and looks only for RPATH, it would explain this behavior.

EDIT 2:

This still-open report looks like an excellent candidate for having introduced the observed behavior:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=859732

(discovered through comments on use RPATH but not RUNPATH? )

EDIT 3:

On a commenter's advice, I reexamined updating the ld configuration before building the extension and that resolved the issue! I had tried this before, but must have overlooked something between build attempts:

# echo /opt/oracle/instantclient_12_2 > /etc/ld.so.conf.d/oracle-instantclient.conf
# ldconfig

I still don't know why LD_LIBRARY_PATH doesn't work as it should in this instance, but adding the Instant Client library path to the linker configuration seems a better approach besides.

EDIT 4:

I stated in my previous edit that modifying the ldconfig constitutes a better approach, but came to realize (on a commenter's good advice) that doing so can cause undesirable library conflicts, because the effects are system-wide.

In hindsight, it makes sense to minimize the "collateral damage" from runtime library linkage modifications by limiting them to the execution environment via the LD_LIBRARY_PATH. Accordingly, I am motivated to determine why this does not work on Ubuntu 18.04 LTS.

I feel that I have established definitively that the PHP-FPM daemon ignores LD_LIBRARY_PATH on Ubuntu (and has since at least Ubuntu 16.04 LTS; see Comments for explanation).

The ld.so(8) manpage states (in relation to the order in which runtime library paths are searched):

Using the environment variable LD_LIBRARY_PATH (unless the executable is being run in secure-execution mode; see below). [sic] in which case it is ignored.

As yet, I cannot think of any other reason for which the path would be ignored. Of secure-execution mode, the same document says:

 Secure-execution mode
       For  security reasons, the effects of some environment variables are voided or modified if the dynamic linker determines that the binary
       should be run in secure-execution mode.  (For details, see the discussion of individual environment variables below.)  A binary is  exe‐
       cuted  in  secure-execution  mode if the AT_SECURE entry in the auxiliary vector (see getauxval(3)) has a nonzero value.  This entry may
       have a nonzero value for various reasons, including:

       *  The process's real and effective user IDs differ, or the real and effective group IDs differ.  This typically occurs as a  result  of
          executing a set-user-ID or set-group-ID program.

       *  A process with a non-root user ID executed a binary that conferred capabilities to the process.

       *  A nonzero value may have been set by a Linux Security Module.

Firstly, Secure-Execution Mode seems not to be in effect, as the PHP executables don't exhibit this flag (AT_SECURE is 0):

LD_SHOW_AUXV=1 /usr/sbin/php-fpm7.1 -daemonize --fpm-config /etc/php/7.1/fpm/php-fpm.conf
AT_SYSINFO_EHDR: 0x7ffc569e1000
AT_HWCAP:        178bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x55ceab0c4040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f823c77f000
AT_FLAGS:        0x0
AT_ENTRY:        0x55ceab19e360
AT_UID:          0
AT_EUID:         0
AT_GID:          0
AT_EGID:         0
AT_SECURE:       0
AT_RANDOM:       0x7ffc56962349
AT_HWCAP2:       0x0
AT_EXECFN:       /usr/sbin/php-fpm7.1
AT_PLATFORM:     x86_64

It occurred to me that the child FPM pool processes might exhibit different AT_SECURE values, but the output is identical for the PHP-FPM daemon itself, as well as any child processes. The parent and the children all have the following values:

# od -t d8 /proc/851/auxv
0000000                   33      140722944548864
0000020                   16            395049983
0000040                    6                 4096
0000060                   17                  100
0000100                    3       93903778242624
0000120                    4                   56
0000140                    5                    9
0000160                    7      140365152313344
0000200                    8                    0
0000220                    9       93903779136352
0000240                   11                    0
0000260                   12                    0
0000300                   13                    0
0000320                   14                    0
0000340                   23                    0
0000360                   25      140722944193929
0000400                   26                    0
0000420                   31      140722944196579
0000440                   15      140722944193945
0000460                    0                    0

Secondly, none of these reasons seem to apply, given the following:

1) There is no indication that PHP-FPM or its child processes have real and effective user or group IDs that differ (thanks to https://unix.stackexchange.com/a/202359 for this command):

# ps -e -o user= -o ruser= | awk '$1 != $2'
systemd+ systemd-timesync
systemd+ systemd-resolve
beansta+ beanstalkd
message+ messagebus
daemon   root
systemd+ systemd-network

# ps -e -o group= -o rgroup= | awk '$1 != $2'
systemd+ systemd-timesync
systemd+ systemd-resolve
beansta+ beanstalkd
message+ messagebus
daemon   root
systemd+ systemd-network

2) The binaries in question do not have any capabilities (the following commands produce no output):

# getcap /usr/lib/php/20170718/oci8.so
# getcap -r /opt/oracle/instantclient_12_2/

3) I have ensured that AppArmor is disabled (it doesn't have a policy that should affect PHP-FPM, anyway):

# systemctl disable apparmor
Synchronizing state of apparmor.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable apparmor
# reboot
# aa-status
apparmor module is loaded.
0 profiles are loaded.
0 profiles are in enforce mode.
0 profiles are in complain mode.
0 processes have profiles defined.
0 processes are in enforce mode.
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

So, why does PHP-FPM ignore LD_LIBRARY_PATH, if not for any of the aforementioned reasons?

EDIT 5 (Solution):

An astute commenter, @vinc17 , points-out that on systems running systemd, environment variables, such as LD_LIBRARY_PATH, are not necessarily propagated to processes that are started via a systemd Unit.

In other words, PHP-FPM isn't "ignoring" LD_LIBRARY_PATH, but rather, it is not being conveyed to the process. And attempts to set LD_LIBRARY_PATH within the PHP-FPM configuration are futile, because it's too late to do anything useful with the value.

On this advice, it occurred to me to set LD_LIBRARY_PATH in the systemd context, namely, in the Unit file(s) that start the PHP-FPM daemon(s), in which case PHP-FPM loads the OCI8 extension successfully.

Needless to say, we want to avoid editing the package maintainer's file (to avoid conflicts during future upgrades), so we extend it instead:

# mkdir /etc/systemd/system/php7.1-fpm.service.d
# touch /etc/systemd/system/php7.1-fpm.service.d/environment.conf

To this file we add the following:

[Service]
Environment=LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2

And to make the change effective:

# systemctl daemon-reload
# systemctl restart php7.1-fpm

For a more complete example, which addresses multiple co-installed PHP versions, please see my post at https://github.com/oerdnj/deb.sury.org/issues/865#issuecomment-395441936 .

Ben Johnson
  • 2,507
  • 3
  • 29
  • 29
  • Very nice analysys, though i do not understand your solution (Edit5) completely. That is now only the solution for php-fpm? Does your php cli work with oci8? I had the problem with apache and php cli so i use the solution of Edit3. – Gunni Jul 04 '18 at 08:43
  • @Gunni Thanks! The solution described in Edit 5 is relevant only when the PHP-FPM daemon is started from within systemd. Also, Edit 5 is not the *only* solution; the one in Edit 3 will "work", but it has drawbacks (see Comments on Answer). For PHP CLI, all that is necessary is `echo 'LD_LIBRARY_PATH="/opt/oracle/instantclient_12_2"' >> /etc/environment`. As for Apache's mod_php, while I haven't tested it, I suspect that if it's started from within systemd, the Edit 3 or Edit 5 solution will be necessary (I doubt that the "old" method at https://stackoverflow.com/a/45242468/1772379 will work). – Ben Johnson Jul 04 '18 at 12:23
  • For me the "old" solutiond did work with modapache in Ubuntu 18.04. I choose the third method anyways because it is the solution for me that has the least file count to edit to "just make it work" (tm) everywhere(apache, cli). I read about the possible conflicts, but for me these do not apply. – Gunni Jul 05 '18 at 16:55

1 Answers1

1

First, Debian bug 859732 is a completely different issue (I would even say an opposite issue): for this bug, several versions of the library are present in the search path (one in some directory specified by LD_LIBRARY_PATH and a different one in some directory specified by the run path), but the wrong one is chosen by the dynamic linker.

In your case, the problem is that the requested library is not found anywhere in the search path. Note also that in your case, it is PHP that seems to try to open the library (via dlopen?), since the message starts with "PHP Warning:". However, it seems that the mechanisms are the same as with usual dynamic linking.

After installing the library, what you need is at least one of:

  • Nothing special if the library has been installed in a directory that is searched by default. Since you get an error, this is not your case.
  • Providing the directory in a run path, which must be specified at compile time of the software that will need the library. The problem is that under Linux, this is not done in standard by the build tools, and it may be complex to do it right without breaking other things. However, in the context of dlopen, the software (here, PHP) may have set up what one can call a "plugin search path", where you can put your libraries.
  • Providing the directory in LD_LIBRARY_PATH. This is what you tried, but your LD_LIBRARY_PATH seems incorrect. Libraries are usually installed in subdirectories named lib (or lib32 or lib64 in specific cases). So, export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 seems wrong. Search for the full pathname of the library oci8.so, and just take the directory part of this pathname for LD_LIBRARY_PATH.

Note: strace may be useful to see what directories are considered to search for the libraries. EDIT: ldd and objdump -p are other useful tools to find what's going on with the search paths.

EDIT 2: Another point to note when choosing to use a run path is that indirect library dependencies are found when RPATH is used, but not when RUNPATH is used (so, in this latter case, all dependencies also need to have a run path if they depend on other libraries so that all libraries can be found without resorting to LD_LIBRARY_PATH). This is documented in recent versions of the ld.so(8) man page:

Using the directories specified in the DT_RUNPATH dynamic section attribute of the binary if present. Such directories are searched only to find those objects required by DT_NEEDED (direct dependencies) entries and do not apply to those objects' children, which must themselves have their own DT_RUNPATH entries. This is unlike DT_RPATH, which is applied to searches for all children in the dependency tree.

This is probably why, without using LD_LIBRARY_PATH, this was working with 16.04 (where RPATH is used) but not with 18.04 (where RUNPATH is used).

vinc17
  • 2,829
  • 17
  • 23
  • Thanks so much to taking a look! The file referenced in the error message does exist at `/opt/oracle/instantclient_12_2/libmql1.so`; I'm simply following Oracle's own instructions, from http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html#ic_x64_inst . The actual extension that PHP tries to load is at `/usr/lib/php/20170718/oci8.so`. In other words, it seems that `oci8.so` is found, but fails to load because it tries to find `libmql1.so` but can't. Given that both Apache and PHP-CLI load the same `oci8.so` without issue implies that LD_LIBRARY_PATH is being ignored in PHP-FPM. – Ben Johnson Jun 01 '18 at 17:14
  • Great idea re: `strace`. I was able to capture the php-fpm daemon startup and it sheds some light on this: https://gist.github.com/cbj4074/e079a58fa1bd8c0e0a2a9c04ec4043e8 . I see no mention of `LD_LIBRARY_PATH` or `instantclient` in the output. Also, I find it odd that PHP tries to load `oci8.so.so` (note the duplicate extension), but maybe that is just some fallback mechanism in case the PHP configuration has `extension=oci8` instead of `extension=oci8.so` (the former seems more "portable"). It seems clear that the true failure is the inability to find `libmql1.so`. – Ben Johnson Jun 01 '18 at 17:20
  • I was incorrect in my previous comment and amended the Gist to include further relevant output. You are spot-on in that the `LD_LIBRARY_PATH` given in Oracle's own instructions seems non-standard. Even so, when PHP reads `oci8.so`, all of the other `.so` files upon which it depends are found (they, too, reside in `/opt/oracle/instantclient_12_2`); it's only `libmql1.so` that fails to be found, and `LD_LIBRARY_PATH` is never searched (even though it *is* searched for all other related `.so` files). Any idea why, or how to fix it? – Ben Johnson Jun 01 '18 at 18:40
  • Wrong again I was! `LD_LIBRARY_PATH` *is* considered, but only for one of the libraries, `/opt/oracle/instantclient_12_2/libclntsh.so.12.1`, which `oci8.so` seems to read first. There are in fact 5 `.so` objects that fail to be found because `LD_LIBRARY_PATH` is not checked, all of which reside in `/opt/oracle/instantclient_12_2`. I discovered this by creating a symlink for each failure, e.g., `ln -s /opt/oracle/instantclient_12_2/libmql1.so /usr/lib/libmql1.so`, in order to see the next failure in the list. With all 5 symlinks in place, `oci8.so` loads correctly. So, is this, in fact, a bug? – Ben Johnson Jun 01 '18 at 20:07
  • 1
    @BenJohnson `strace` gives a trace of system calls, so that you will never see a mention of the string `LD_LIBRARY_PATH` there, but you may be able to see directories it provides, e.g. in `/opt/oracle/instantclient_12_2/libclntsh.so.12.1` as you noticed. The fact that `LD_LIBRARY_PATH` is not taken into account for the other libraries may be a bug in PHP (it might also be a security feature, in which case the fact that `libclntsh.so.12.1` was found there may be a vulnerability). I suggest that you contact the PHP developers. – vinc17 Jun 01 '18 at 23:17
  • 1
    @BenJohnson Hmm... After some thoughts, `oci8.so` was found (possibly from some run path in PHP), but the issue is that one of its requisites (`libmql1.so`) could not be loaded. Still, I don't understand why `LD_LIBRARY_PATH` was taken into account for `libclntsh.so.12.1` but not for `libmql1.so`. Probably a different mechanism. `ldd` (with and without `LD_LIBRARY_PATH` set) and `objdump -p` on the libraries may give additional interesting information. – vinc17 Jun 01 '18 at 23:39
  • 1
    Problems like this are why the [Instant Client install doc](http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html#ic_x64_inst) tries to get you to use ldconfig instead of LD_LIBRARY PATH. – Christopher Jones Jun 04 '18 at 05:08
  • @ChristopherJones I tried `ldconfig` first, as noted in step 3 at https://github.com/oerdnj/deb.sury.org/issues/865#issuecomment-390247190 . It makes no difference. – Ben Johnson Jun 04 '18 at 14:57
  • @vinc17 Excellent insights, again. Please see https://gist.github.com/cbj4074/5104d4d7218836a3bd38d0c0b25f3340 for the requested output. I captured the values only for `oci8.so` and `libclntsh.so.12.1`, as I assume that the remaining dependent libraries would provide the same info. – Ben Johnson Jun 04 '18 at 15:13
  • @ChristopherJones I went to double-check the assertion in my previous comment to you, on a completely clean VM, and lo, using `ldconfig` instead of `LD_LIBRARY_PATH` works! I'm positive that I had tried using `ldconfig` before, but I had already built the extension once without it, and I'm wondering if I failed to fully clear or otherwise purge the build environment after running `ldconfig` and rebuilding. Thank you for pointing that out, and my apologies for the bad intel earlier! I really appreciate the help! I'd upvote your comment twice if I could! – Ben Johnson Jun 04 '18 at 19:55
  • 1
    @BenJohnson It's strange that `ldd` can find `libmql1.so` (in `/usr/lib`, though not in the expected place) but it is not found via PHP. Anyway, as you can see, `/usr/lib/php/20170718/oci8.so` has a `RUNPATH` with `/opt/oracle/instantclient_12_2`, but not `/opt/oracle/instantclient_12_2/libclntsh.so.12.1`, and I think that this should have been the way to go for consistency. Note also that adding `/opt/oracle/instantclient_12_2` to the default dirs with `ldconfig` may be a bad idea as this is global to the machine and could conflict with other software. – vinc17 Jun 05 '18 at 08:13
  • @vinc17 I recaptured all that output on clean VMs, triple-checking my work, so please use https://www.diffchecker.com/kpJtFoBm as the definitive reference; Ubuntu 16.04 (where this works fine) is on the left, and 18.04 (where it's "broken") on the right. The behavior diverges when we `unset LD_LIBRARY_PATH`: on 16.04, the libs are still found, but on 18.04 they are not. As you noted, `oci8.so` has a `RUNPATH`, but `libclntsh.so.12.1` does not. The latter ships directly from Oracle (in the download), so I have no ability to change that. Seems I have no choice but to debug `LD_LIBRARY_PATH`. – Ben Johnson Jun 05 '18 at 13:51
  • 1
    @BenJohnson I've found the explanation. See my second edit. Basically, this is due to the change of `RPATH` to `RUNPATH`, which behaves differently concerning indirect library dependencies. Note that this is still unrelated to [Debian bug 859732](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=859732), which is due to another difference between `RPATH` and `RUNPATH`. – vinc17 Jun 05 '18 at 23:58
  • @vinc17 Excellent find! So, if I understand all of this correctly, the only means by which to avoid `ldconfig` changes on 18.04 are to: a) specify `RUNPATH` for `libclntsh.so.12.1` (which I cannot do if I download the file directly from Oracle); or b) use `LD_LIBRARY_PATH`. But given that the PHP-FPM daemon ignores `LD_LIBRARY_PATH`, for whatever reason, neither option is viable. `ld.so(8)` notes that `LD_LIBRARY_PATH` is ignored or modified in `Secure-execution mode`. Of the three causes noted, none seem to apply. Executing the daemon with `LD_SHOW_AUXV=1` yields `AT_SECURE: 0`. I'm stumped! – Ben Johnson Jun 06 '18 at 13:15
  • @vinc17 I've updated the OP to include an exhaustive explanation of why secure-execution mode seems unlikely to be the reason for which PHP-FPM ignores `LD_LIBRARY_PATH`. Also, I may have overlooked another option: compiling `oci8.so` using statically-linked libraries. But, as with `ldconfig`, this this feels like an unnecessary workaround that comes with baggage. Despite the info at https://blogs.oracle.com/opal/the-php-quotconfigure-with-oci8quot-option-in-detail , it's still not clear whether it's possible to compile a shared `oci8.so` that links to the Oracle libs *statically*. Any idea? – Ben Johnson Jun 06 '18 at 14:40
  • 1
    @BenJohnson I don't know how to compile a shared `oci8.so` that links to the Oracle libs statically, but this would be possible only if the static Oracle libs are PIC. https://stackoverflow.com/questions/13989745/compiling-pic-object-with-static-library may give you some info. Concerning `LD_LIBRARY_PATH`, the cause of the failure may be that it is not defined early enough, i.e. at the time PHP tries to open the libraries. If you're using *systemd*, search for [systemd ld_library_path](https://www.google.com/search?q=systemd+ld_library_path) and look at solutions and bugs... – vinc17 Jun 06 '18 at 21:28
  • 1
    @vinc17 Bingo! That was it! It never occurred to me that systemd might be a factor here. If I add `Environment=LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2` to the `[Service]` section of the unit file (`/lib/systemd/system/php7.1-fpm.service`, in this case), the extension loads perfectly! Color me impressed! Very fine troubleshooting work here. I can't thank you enough!!! – Ben Johnson Jun 07 '18 at 13:28