Elimination of file descriptor inheritance in multiple processes

Elimination of file descriptor inheritance in multiple processes

What is file descriptor inheritance

Basic operations for reading directories

Elimination of file descriptors

to sum up


What is file descriptor inheritance

     When a parent process creates a child process, regardless of the fork function or the vfork function, the child process usually inherits the file descriptor of the parent process. The so-called inheritance means that the child process can use the same file descriptor and operate the same file object as the parent process. as the picture shows

Parent-child process shared file descriptor

     This may cause potential security risks. How to do it?

      The easiest way of course is to do nothing. Tell developers that this way of sharing file objects between parent and child processes is very dangerous. You can do it yourself, except for things yourself. Of course, this method of processing is the least expensive for the executive library because you don’t need to add any code. , At most, write two explanations on the document. Obviously, this method is a desperate method.

     The second method is to close the inherited file descriptor immediately after creating the child process. The close function is generally used to close the file descriptor. The only trouble is how do you know which file descriptors the process has opened?

Basic operations for reading directories

Usually in a Linux system, the /proc directory records the runtime information of each process. Each process in /proc has a directory named after the process ID. And in the directory of each process, there is another directory named "fd". The description of the file opened by the process corresponds to a symbolic link file in this directory, and the name of the file is the value of the file descriptor. Okay, now it seems that the problem is much easier to handle. As long as we can traverse the "/proc/PID/fd" directory, we can get the file descriptor opened by the current process, and then we can call the close function for it.

So, how to traverse a directory?

To traverse a directory, first call the opendir function to open the directory, then call the readdir function to get the names of the subdirectories and files in the directory, and finally call the closedir function to close the directory. Well, let's first look at the prototypes of these functions.

#include <dirent.h>
  DIR* opendir(const char* name)    

To read the directory, you need to call opendir first. This function receives one parameter, that is, the name of the directory that needs to be opened. Of course, this name needs to carry path information. When the opendir function is successfully called, it will return a pointer to the DIR structure, and it will return 0 if it fails. The pointer of this DIR structure will be used in subsequent calls of readdir and closedir functions. Okay, let's take a look at the prototype of the readdir function:

#include <dirent.h>
struct dirent* readdir(DIR *dirp);

The readdir function only receives one parameter, which is the return value of the opendir function, which means to read information from the directory specified when the opendir function is called. Calling the readdir function once will return a structure instance pointer related to a file or subdirectory in the directory, that is, the dirent structure pointer. The next time the readdir function is called, it will return the structure instance pointer corresponding to the next file or subdirectory in the directory.

After all files or subdirectories have been returned, calling the readdir function will return 0. In this way, you can get the dirent structure instance of all files or subdirectories in the specified directory. So now, where is the file name we need? In fact, the dirent structure has a d_name field, which is a string pointer to the file name or subdirectory name. When we no longer need to read the directory, in order to release resources, we need to call the closedir function to close the directory. The prototype of this function is as follows:

#include <dirent.h>
int closedir(DIR* dirp);

Elimination of file descriptors

Similar to the readdir function, the closedir function only receives one parameter, which is the pointer to the DIR structure returned by the opendir function. If the closedir function is successfully called, it returns 0, otherwise it returns -1. Let's take a look at how the specific code is implemented.

   1 int Process::CloseFileDescriptor()                                                                                                       |
   2 {                                                                                                                                           
   3     string strPath = "/proc/";                                                                                                              
   4                                                                                                                                             |
   5     char id[LENGTH_OF_PROCESSID];                                                                          
   6     snprintf(id, LENGTH_OF_PROCESSID, "%d", m_ProcessID);                                                                                   
   7                                                                                                                                         |    
   8     strPath += id;                                                                                                                          
   9     strPath += "/fd";                                                                                                                       
  10     string strPath1 = strPath;                                                                                                                                                                                                                                                        
  11     strPath += "/";                                                                                                                         |~                             
  12                                                                                                                                            |~                             
  13     DIR *pDir = opendir(strPath.c_str());                                                                                                   |~                             
  14     if(pDir == 0)                                                                                                                           |~                             
  114     {                                                                                                                                       |~                             
  15             Logger::WriteLogMsg("In Process::CloseFileDescriptor(), opendir error", 0);                                                |~                 
  16             return -1;                                                                                                               |~                             
  17     }                                                                                                                                       |~                             
  18                                                                                                                                             |~                             
  19     while(struct dirent *pDirent = readdir(pDir))                                                                                           |~                             
  20     {                                                                                                                                       |~                             
  21         char captial = pDirent->d_name[0];                                                                                                  |~                             
  22         if((captial == '.') || (captial == '0') || (captial == '1') || (captial == '2'))                                                    |~                             
  23             continue;                                                                                                                       |~                             
  24                                                                                                                                             |~                             
  25          int fd = atoi(pDirent->d_name);                                                                                                     |~                             
  26          if(fd != 0)                                                                                                                         |~                             
  27         {                                                                                                                                   |~                             
  28             if(close(fd) == -1)                                                                                                             |~                             
  29             {                                                                                                                               |~                             
  30                 string errormsg = "In Process::CloseFileDescriptor(), close error, file: ";                                                 |~                             
  31                 errormsg += pDirent->d_name;                                                                                                |~                             
  32                 std::cout << errormsg << std:endl;                                                                               |~                             
  33             }                                                                                                                               |~                             
  34         }                                                                                                                                   |~                             
  35     }                      
  36    
  37    if(closedir(pDir) == -1)                                                                                                                |~                             
  38   {                                                                                                                                       |~                             
  39          Logger::WriteLogMsg("In Process::CloseFileDescriptor(), closedir error", errno);                                                |~                             
  40         return -1;                                                                                                               |~                             
  41     }                                                                                                                                       |~                             
  42                                                                                                                                             |~                             
  43     return 0;                                                                                                                    |~                             
  144 }                  

This function is placed in the member function of creating a new process as a private member function of the process class Process. I will not introduce the process class I encapsulated in detail here. The main logic of the declaration code of the Process class and the Run() method of creating a child process is as follows:

   #include <unistd.h>                                                                                                                           |
  ......                                                                                                                        |▼ Process : class
 1                                                                                                                                              |    [prototypes]
 2  class Process : public Executive                                                                                                              |   -CloseFileDescriptor()
 3  {                                                                                                                                             |   +Process(ExecutiveFunctionP
 4  public:                                                                                                                               |   -Process(const Process&)
 5    ......
 6     Process(ExecutiveFunctionProvider *pExecutiveFunctionProvider, bool bWaitForDeath);                                                       |   +~Process()
 12                                                                                                                                               |   +Run(void *pstrCmdLine = 0)
 13     virtual ~Process();                                                                                                                       |   +WaitForDeath()
 14                                                                                                                                               |   -operator =(const Process&)
 15     virtual int Run(void *pstrCmdLine = 0);                                                                                                |    [members]
 16     virtual int WaitForDeath();                                                                                                            |   -m_ProcessID
 17                                                                                                                                               |   -
 18 private:                                                                                                                                      |   -
 19     int CloseFileDescriptor();                                                                                                             |   -
 20                                                                                                                                               |~                             
 21 private:                                                                                                                                      |~                             
 22     Process(const Process&);                                                                                                                  |~                             
 23     Process& operator=(const Process&);                                                                                                       |~                             
 24                                                                                                                                               |~                             
 25     pid_t m_ProcessID;                                                                                                                        |~                             
 26                                                                                                                                               |~                             
 28    ......                                                                                                                 |~                             
 29 };                          

Next, continue to discuss the CloseFileDescriptor function. From the code snippet above, we can see that the main logic of the function is to first form the directory address to be accessed "/proc/process ID/fd" and call the opendir function on line 13 to open the specified directory, and then Enter a while loop to traverse all files in the directory.

These files are the values ​​of file descriptors that the process has opened. When dealing with these files, there are several special files that need to be treated differently, such as  . and  ... Represents the current directory and the parent directory respectively, so ignore them. The files named 0, 1, and 2 obviously correspond to standard input, standard output, and standard error. These file descriptors should not be closed, otherwise the child process cannot perform input and output normally even after successfully executing the execv function.

Therefore, the child process does not close the above file descriptor in line 22. After that, the child process converts the file descriptor in string form into a value in line 25, and then calls the close function on line 28 to close the file. After exiting the loop of traversing the files, the child process calls the closedir function on line 37 to close the directory just opened. If the shutdown fails, log information is recorded.

The main implementation is finished, let's take a look at this function through a test code. The test code is as follows:

    1 int main(int argc, char *argv[])
    2 {
    3     cout << "in child main" << endl;
    4 
    5     if(write(3, "nihao", 5) == -1)
    6     cout << "child write error" << endl;
    7     else
    8     cout << "child write success" << endl;
    9 
   10     return 0;
   11 }

This test code is very simple, first output "in child main" to the screen on line 3, and then call the write function on line 5 to write a message "nihao" to file descriptor 3, and print it according to the return value of the write function Out the corresponding information. Under normal circumstances, except for 0, 1, and 2, the rest should have been closed. Therefore, the write function should return an error. Now we look at the results of the executable program:

Seeing the results of this operation, everyone will be surprised. Actually output "child write success". In other words, after the child process executed the main function above, the information written to file descriptor 3 was successfully written.

And which file does this file descriptor 3 correspond to? Let's open the log file to see:

Insert picture description here

As you can see, the content in the log file is exactly the "nihao" written to file descriptor 3. In other words, file descriptor 3 corresponds to the log file. This is weird. The CloseFileDescriptor function looks for the one. Hasn't it closed all file descriptors other than 0, 1, and 2, why hasn't 3 been closed? We are looking at the code of the CloseFileDescriptor function above. The closedir call at line 37 failed. As soon as it failed, the log was written immediately. Because neither the parent process nor the child process have written logs before. WriteLogMsg here will cause the log object to be created, and after the open function is called to open the log file in the log object construction, the returned file descriptor should be 3.

So, what happened to the closedir function call failure here? In Linux systems, whether it is a directory or an ordinary file, it is actually just one type of file. Then when the opendir function opens a directory, it will actually perform operations similar to the open function, and will also get the file descriptor representing the open directory; similarly, when the closedir function closes the directory, it will also close the file descriptor of the open directory. However, in the directory file traversal code, except for 0, 1, and 2, other file descriptors are closed. Therefore, when the closedir function is called, the closed file descriptor is closed again, of course. . Well, the reason for the closedir function call error has been found, and the rest is how to solve it.

You can observe the files in the "/proc/process ID/fd" directory on the Linux system, and you will find that they are actually symbolic link files. These symbolic link files are similar to shortcuts in Windows systems, which point to another file. If we can get the name of the file actually pointed to by the symbolic link file when traversing the files in the directory, then things will be much easier.

We can compare this file name with "/proc/process ID/fd". If it is the same, it means that the directory corresponding to the file descriptor currently being processed is the directory we traverse. Do not call close for it. Function instead of waiting for the directory traversal to complete and call the closedir function to close it. Now let's see if we read the file name actually pointed to by the symbolic link file. This is very simple. You can directly call the readlink function. The prototype of the function is given below:

#include <unistd.h>
ssize_t readlink(const char* path, char* buf, size_t bufsize);

The readlink function receives 3 parameters, the first is the path information of the symbolic link file to be read; when the file name actually pointed to by the symbolic link file is read, the readlink function will write it to the second parameter buf In the cache; and the third parameter bufsize, which is the size of the cache pointed to by buf. After the readlink function is successfully called, it will return the number of bytes written in the cache, or -1 if it fails. However, one thing needs to be reminded that when this function fills the cache with information, the end will not be filled with 0. Therefore, the initialization operation of the cache needs to be completed by the caller. Okay, now we modify the CloseFileDescriptor function just now.

  1   int Process::CloseFileDescriptor()                                                                                                       |▶ macros
     {                                                                                                                                           |
  2      string strPath = "/proc/";                                                                                                              |▼ Process* : class
  3                                                                                                                                             |    [functions]
  4      char id[LENGTH_OF_PROCESSID];                                                                                                           |    CloseFileDescriptor()
  5      snprintf(id, LENGTH_OF_PROCESSID, "%d", m_ProcessID);                                                                                   |    Process(ExecutiveFunctionP
  6                                                                                                                                             |    Process(ExecutiveFunctionP
  7      strPath += id;                                                                                                                          |    ~Process()
  8      strPath += "/fd";                                                                                                                       |    Run(void *pstrCmdLine)
  9                                                                                                                                             |    WaitForDeath()
  10     string strPath1 = strPath;                                                                                                              |~                             
  11                                                                                                                                             |~                             
  12     strPath += "/";                                                                                                                         |~                             
  13                                                                                                                                             |~                             
  14     DIR *pDir = opendir(strPath.c_str());                                                                                                   |~                             
  15     if(pDir == 0)                                                                                                                           |~                             
  16    {                                                                                                                                       |~                             
  17         Logger::WriteLogMsg("In Process::CloseFileDescriptor(), opendir error", 0);                                                         |~                             
  18         return -1;                                                                                                               |~                             
  19     }                                                                                                                                       |~                             
  20                                                                                                                                             |~                             
  21     while(struct dirent *pDirent = readdir(pDir))                                                                                           |~                             
  22     {                                                                                                                                       |~                             
  23         char captial = pDirent->d_name[0];                                                                                                  |~                             
  24         if((captial == '.') || (captial == '0') || (captial == '1') || (captial == '2'))                                                    |~                             
  25             continue;                                                                                                                       |~                             
  26                                                                                                                                             |~                             
  27         int fd = atoi(pDirent->d_name);                                                                                                     |~                             
  28         if(fd != 0)                                                                                                                         |~                             
  29         {                                                                                                                                   |~                             
  30             string strTmpPath = strPath;                                                                                                    |~                             
  31             strTmpPath += pDirent->d_name;                                                                                                  |~                             
  32                                                                                                                                             |~                             
  33             char pathname[LENGTH_OF_PATH] = {0};                                                                                            |~                             
  34             if(readlink(strTmpPath.c_str(), pathname, LENGTH_OF_PATH) == -1)                                                                |~                             
  35             {                                                                                                                               |~                             
  36                 Logger::WriteLogMsg("In Process::CloseFileDescriptor(), readlink error", errno);                                            |~                             
  37                 continue;   
  38                }                                                                                                                               |~                             
  39                                                                                                                                             |~                             
  40             if(strcmp(pathname, strPath1.c_str()) == 0)                                                                                     |~                             
  41                 continue;                                                                                                                   |~                                                                                                                                                                          |~                             
  42             if(close(fd) == -1)                                                                                                             |~                             
  43             {                                                                                                                               |~                             
  44                 string errormsg = "In Process::CloseFileDescriptor(), close error, file: ";                                                 |~                             
  45                 errormsg += pDirent->d_name;                                                                                                |~                             
  46                 Logger::WriteLogMsg(errormsg.c_str(), errno);                                                                               |~                             
  47             }                                                                                                                               |~                             
  48         }                                                                                                                                   |~                             
  49     }                                                                                                                                       |~                             
  50                                                                                                                                             |~                             
  51     if(closedir(pDir) == -1)                                                                                                                |~                             
  52     {                                                                                                                                       |~                             
  53         Logger::WriteLogMsg("In Process::CloseFileDescriptor(), closedir error", errno);                                                    |~                             
  54         return -1;                                                                                                               |~                             
  55     }                                                                                                                                       |~                             
  56                                                                                                                                             |~                             
  57     return 0;                                                                                                                    |~                             
  58 }                            

The above code is a modification of the CloseFileDescriptor function. The main change is that when traversing the directory file, the readlink function is called to get the file name actually pointed to by the symbolic link file, which is line 34. Then compare the name with "/proc/process ID/fd" on line 40. If they are the same, ignore them. After waiting to exit the loop, call the closedir function to close the directory. Then the problem of writing file descriptor 3 like this is solved.

to sum up

A series of problems caused by the inherited document description, here we have come to an end. We discussed how to traverse the directory to get the file descriptor that the process has opened, and how to read the symbolic link file to avoid the directory descriptor being used. If you are interested, you can pay attention to my future feature article.

Guess you like

Origin blog.csdn.net/songguangfan/article/details/113736135