Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle readlink() of "/proc/self/exe" when executable is replaced during execution?

In my C++ application, my application does an execv() in a fork()ed child process to use the same executable to process some work in a new child process with different arguments that communicates with pipes to the parent process. To get the pathname to self, I execute the following code on the Linux port (I have different code on Macintosh):

  const size_t bufSize = PATH_MAX + 1;
  char dirNameBuffer[bufSize];
  // Read the symbolic link '/proc/self/exe'.
  const char *linkName = "/proc/self/exe";
  const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));

However, if while the executable is running, I replace the executable with an updated version of the binary on disk, the readlink() string result is: "/usr/local/bin/myExecutable (deleted)"

I understand that my executable has been replaced by a newer updated version and the original for /proc/self/exe is now replaced, however, when I go to execv() it now fails with the errno 2 - No such file or directory. due to the extra trailing " (deleted)" in the result.

I would like the execv() to either use the old executable for self, or the updated one. I could just detect the string ending with " (deleted)" and modify it to omit that and resolve to the updated executable, but that seems clumsy to me.

How can I execv() the current executable (or its replacement if that is easier) with a new set of arguments when the original executable has been replaced by an updated one during execution?

like image 921
WilliamKF Avatar asked Mar 09 '15 23:03

WilliamKF


3 Answers

One solution is at executable startup (e.g. near the beginning of main()) to read the value of the link /proc/self/exe once and store it statically for future use:

  static string savedBinary;
  static bool initialized = false;

  // To deal with issue of long running executable having its binary replaced
  // with a newer one on disk, we compute the resolved binary once at startup.
  if (!initialized) {
    const size_t bufSize = PATH_MAX + 1;
    char dirNameBuffer[bufSize];
    // Read the symbolic link '/proc/self/exe'.
    const char *linkName = "/proc/self/exe";
    const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));

    savedBinary = dirNameBuffer;

    // On at least Linux, if the executable is replaced, readlink() of
    // "/proc/self/exe" gives "/usr/local/bin/flume (deleted)".
    // Therefore, we just compute the binary location statically once at
    // startup, before it can possibly be replaced, but we leave this code
    // here as an extra precaution.
    const string deleted(" (deleted)");
    const size_t deletedSize = deleted.size();
    const size_t pathSize = savedBinary.size();

    if (pathSize > deletedSize) {
      const size_t matchPos = pathSize - deletedSize;

      if (0 == savedBinary.compare(matchPos, deletedSize, deleted)) {
        // Deleted original binary, Issue warning, throw an exception, or exit.
        // Or cludge the original path with: savedBinary.erase(matchPos);
      }
    }
    initialized = true;
  }

  // Use savedBinary value.

In this way, it is very unlikely that the original executable would be replaced within the microseconds of main() caching the path to its binary. Thus, a long running application (e.g. hours or days) could get replaced on disk, but per the original question, it could fork() and execv() to the updated binary that perhaps has a bug fix. This has the added benefit of working across platforms, and thus the differing Macintosh code to read the binary path could be likewise protected from binary replacement after startup.

WARNING editors note: readlink does not null terminate the string, so the above program may or may not work accidentally if the buffer was not filled with zeros before calling readlink

like image 106
WilliamKF Avatar answered Nov 15 '22 16:11

WilliamKF


Instead of using readlink to discover the path to your own executable, you can directly call open on /proc/self/exe. Since the kernel already has an open fd to processes that are currently executing, this will give you an fd regardless of whether the path has been replaced with a new executable or not.

Next, you can use fexecve instead of execv which accepts an fd parameter instead of a filename parameter for the executable.

int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);

Above code omits error handling for brevity.

like image 35
andrewrk Avatar answered Nov 15 '22 16:11

andrewrk


The reason you get the (deleted) part into the symbolic link is that you have substituted the file with the right program binary text with a different file, and the symbolic link to the executable is never valid again. Suppose you use this symbolic link to get the symbol table of this program or to load some data embedded on it, and you change the program... the table would be incorrect and you can even crash your program. The executable file for the program you were executing is no longer available (you have deleted it) and the program you have put in its place doesn't correspond to the binary you are executing.

When you unlink(2) a program that is being executed, the kernel marks that symlink in /proc, so the program can

  • detect that the binary has been deleted and is no longer accessible.
  • allow you still to gather some information of the last name it had (instead of deleting the symlink from the /proc tree)

You cannot write to a file that is being executed by the kernel, but nobody prevents you to erase that file. The file will continue to be present in the filesystem as long as you execute it, but no name points to it (it's space will be deallocated once the process exit(2)) The kernel doesn't erase its contents until the inode count in kernel memory gets to zero, which happens when all uses (references) to that file are due.

like image 45
Luis Colorado Avatar answered Nov 15 '22 16:11

Luis Colorado