Imported Upstream version 17.2.0
[platform/upstream/libzypp.git] / zypp / misc / CheckAccessDeleted.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/misc/CheckAccessDeleted.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <unordered_set>
15 #include <iterator>
16 #include <stdio.h>
17 #include "zypp/base/LogTools.h"
18 #include "zypp/base/String.h"
19 #include "zypp/base/Gettext.h"
20 #include "zypp/base/Exception.h"
21
22 #include "zypp/PathInfo.h"
23 #include "zypp/ExternalProgram.h"
24 #include "zypp/base/Regex.h"
25 #include "zypp/base/IOStream.h"
26 #include "zypp/base/InputStream.h"
27
28 #include "zypp/misc/CheckAccessDeleted.h"
29
30 using std::endl;
31
32 #undef ZYPP_BASE_LOGGER_LOGGROUP
33 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
34
35 ///////////////////////////////////////////////////////////////////
36 namespace zypp
37 { /////////////////////////////////////////////////////////////////
38
39   ///////////////////////////////////////////////////////////////////
40   namespace
41   { /////////////////////////////////////////////////////////////////
42     //
43     // lsof output lines are a sequence of NUL terminated fields,
44     // where the 1st char determines the fields type.
45     //
46     // (pcuL) pid command userid loginname
47     // (ftkn).filedescriptor type linkcount filename
48     //
49     /////////////////////////////////////////////////////////////////
50
51     /** lsof output line + files extracted so far for this PID */
52     typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
53
54     /////////////////////////////////////////////////////////////////
55     /// \class FilterRunsInLXC
56     /// \brief Functor guessing whether \a PID is running in a container.
57     ///
58     /// Assumme using different \c pid namespace than \c self.
59     /////////////////////////////////////////////////////////////////
60     struct FilterRunsInLXC
61     {
62       bool operator()( pid_t pid_r ) const
63       { return( nsIno( pid_r, "pid" ) != pidNS ); }
64
65       FilterRunsInLXC()
66       : pidNS( nsIno( "self", "pid" ) )
67       {}
68
69       static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
70       { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
71
72       static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
73       { return  nsIno( asString(pid_r), ns_r ); }
74
75       ino_t pidNS;
76     };
77   } //namespace
78   /////////////////////////////////////////////////////////////////
79
80   class CheckAccessDeleted::Impl
81   {
82   public:
83     CheckAccessDeleted::Impl *clone() const;
84
85     bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
86     void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
87
88     std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
89     CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
90
91     std::vector<CheckAccessDeleted::ProcInfo> _data;
92     bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
93     bool _verbose = false;
94
95     std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
96     Pathname _debugFile;
97   };
98
99   CheckAccessDeleted::Impl *CheckAccessDeleted::Impl::clone() const
100   {
101     Impl *myClone = new Impl( *this );
102     return myClone;
103   }
104
105   /** Add \c cache to \c data if the process is accessing deleted files.
106    * \c pid string in \c cache is the proc line \c (pcuLR), \c files
107    * are already in place. Always clear the \c cache.files!
108   */
109   inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
110   {
111     const auto & filelist( cache_r.second );
112
113     if ( filelist.empty() )
114       return false;
115
116     // at least one file access so keep it:
117     _data.push_back( CheckAccessDeleted::ProcInfo() );
118     CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
119     pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
120
121     const std::string & pline( cache_r.first );
122     std::string commandname;    // pinfo.command if still needed...
123     std::ostringstream pLineStr; //rewrite the first line in debug cache
124     for_( ch, pline.begin(), pline.end() )
125     {
126       switch ( *ch )
127       {
128         case 'p':
129           pinfo.pid = &*(ch+1);
130           if ( debMap )
131             pLineStr <<&*(ch)<<'\0';
132           break;
133         case 'R':
134           pinfo.ppid = &*(ch+1);
135           if ( debMap )
136             pLineStr <<&*(ch)<<'\0';
137           break;
138         case 'u':
139           pinfo.puid = &*(ch+1);
140           if ( debMap )
141             pLineStr <<&*(ch)<<'\0';
142           break;
143         case 'L':
144           pinfo.login = &*(ch+1);
145           if ( debMap )
146             pLineStr <<&*(ch)<<'\0';
147           break;
148         case 'c':
149           if ( pinfo.command.empty() ) {
150             commandname = &*(ch+1);
151             // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
152             if (!_fromLsofFileMode)
153               pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
154             if ( pinfo.command.empty() )
155               pinfo.command = std::move(commandname);
156             if ( debMap )
157               pLineStr <<'c'<<pinfo.command<<'\0';
158           }
159           break;
160       }
161       if ( *ch == '\n' ) break;         // end of data
162       do { ++ch; } while ( *ch != '\0' );       // skip to next field
163     }
164
165     //replace the data in the debug cache as well
166     if ( debMap ) {
167       pLineStr<<endl;
168       debMap->front() = pLineStr.str();
169     }
170
171     //entry was added
172     return true;
173   }
174
175
176   /** Add file to cache if it refers to a deleted executable or library file:
177    * - Either the link count \c(k) is \c 0, or no link cout is present.
178    * - The type \c (t) is set to \c REG or \c DEL
179    * - The filedescriptor \c (f) is set to \c txt, \c mem or \c DEL
180   */
181   inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
182   {
183     const char * f = 0;
184     const char * t = 0;
185     const char * n = 0;
186
187     for_( ch, line_r.c_str(), ch+line_r.size() )
188     {
189       switch ( *ch )
190       {
191         case 'k':
192           if ( *(ch+1) != '0' ) // skip non-zero link counts
193             return;
194           break;
195         case 'f':
196           f = ch+1;
197           break;
198         case 't':
199           t = ch+1;
200           break;
201         case 'n':
202           n = ch+1;
203           break;
204       }
205       if ( *ch == '\n' ) break;         // end of data
206       do { ++ch; } while ( *ch != '\0' );       // skip to next field
207     }
208
209     if ( !t || !f || !n )
210       return;   // wrong filedescriptor/type/name
211
212     if ( !(    ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
213             || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
214       return;   // wrong type
215
216     if ( !(    ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
217             || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
218             || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
219             || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
220       return;   // wrong filedescriptor type
221
222     if ( str::contains( n, "(stat: Permission denied)" ) )
223       return;   // Avoid reporting false positive due to insufficient permission.
224
225     if ( ! _verbose )
226     {
227       if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
228         return; // Try to avoid reporting false positive unless verbose.
229     }
230
231     if ( *f == 'm' || *f == 'D' )       // skip some wellknown nonlibrary memorymapped files
232     {
233       static const char * black[] = {
234           "/SYSV"
235         , "/var/run/"
236         , "/var/lib/sss/"
237         , "/dev/"
238         , "/var/lib/gdm"
239       };
240       for_( it, arrayBegin( black ), arrayEnd( black ) )
241       {
242         if ( str::hasPrefix( n, *it ) )
243           return;
244       }
245     }
246     // Add if no duplicate
247     if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
248       debMap->push_back(line_r);
249     }
250     cache_r.second.insert( n );
251   }
252
253   CheckAccessDeleted::CheckAccessDeleted( bool doCheck_r )
254     : _pimpl(new Impl)
255   {
256     if ( doCheck_r ) check();
257   }
258
259   CheckAccessDeleted::size_type CheckAccessDeleted::check( const Pathname &lsofOutput_r, bool verbose_r )
260   {
261     _pimpl->_verbose = verbose_r;
262     _pimpl->_fromLsofFileMode = true;
263
264     FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
265     if ( !inFile ) {
266       ZYPP_THROW( Exception(  str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
267     }
268
269     //inFile is closed by ExternalDataSource
270     externalprogram::ExternalDataSource inSource( inFile, nullptr );
271     auto cache = _pimpl->filterInput( inSource );
272     return _pimpl->createProcInfo( cache );
273   }
274
275   std::map<pid_t,CacheEntry> CheckAccessDeleted::Impl::filterInput( externalprogram::ExternalDataSource &source )
276   {
277     // cachemap: PID => (deleted files)
278     // NOTE: omit PIDs running in a (lxc/docker) container
279     std::map<pid_t,CacheEntry> cachemap;
280
281     bool debugEnabled = !_debugFile.empty();
282
283     pid_t cachepid = 0;
284     FilterRunsInLXC runsInLXC;
285     for( std::string line = source.receiveLine(); ! line.empty(); line = source.receiveLine() )
286     {
287       // NOTE: line contains '\0' separeated fields!
288       if ( line[0] == 'p' )
289       {
290         str::strtonum( line.c_str()+1, cachepid );      // line is "p<PID>\0...."
291         if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
292           if ( debugEnabled ) {
293             auto &pidMad = debugMap[cachepid];
294             if ( pidMad.empty() )
295               debugMap[cachepid].push_back( line );
296             else
297               debugMap[cachepid].front() = line;
298           }
299           cachemap[cachepid].first.swap( line );
300         } else {
301           cachepid = 0; // ignore this pid
302         }
303       }
304       else if ( cachepid )
305       {
306         auto &dbgMap = debugMap[cachepid];
307         addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
308       }
309     }
310     return cachemap;
311   }
312
313   CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r  )
314   {
315     static const char* argv[] =
316     {
317       "lsof", "-n", "-FpcuLRftkn0", NULL
318     };
319
320     _pimpl->_verbose = verbose_r;
321     _pimpl->_fromLsofFileMode = false;
322
323     ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
324     std::map<pid_t,CacheEntry> cachemap = _pimpl->filterInput( prog );
325
326     int ret = prog.close();
327     if ( ret != 0 )
328     {
329       if ( ret == 129 )
330       {
331         ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
332       }
333       Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
334       err.remember( prog.execError() );
335       ZYPP_THROW( err );
336     }
337
338     return _pimpl->createProcInfo( cachemap );
339   }
340
341   CheckAccessDeleted::size_type CheckAccessDeleted::Impl::createProcInfo(const std::map<pid_t,CacheEntry> &in)
342   {
343     std::ofstream debugFileOut;
344     bool debugEnabled = false;
345     if ( !_debugFile.empty() ) {
346       debugFileOut.open( _debugFile.c_str() );
347       debugEnabled =  debugFileOut.is_open();
348
349       if ( !debugEnabled ) {
350         ERR<<"Unable to open debug file: "<<_debugFile<<endl;
351       }
352     }
353
354     _data.clear();
355     for ( const auto &cached : in )
356     {
357       if (!debugEnabled)
358         addDataIf( cached.second);
359       else {
360         std::vector<std::string> *mapPtr = nullptr;
361
362         auto dbgInfo = debugMap.find(cached.first);
363         if ( dbgInfo != debugMap.end() )
364           mapPtr = &(dbgInfo->second);
365
366         if( !addDataIf( cached.second, mapPtr ) )
367           continue;
368
369         for ( const std::string &dbgLine: dbgInfo->second ) {
370           debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
371         }
372       }
373     }
374     return _data.size();
375   }
376
377   bool CheckAccessDeleted::empty() const
378   {
379     return _pimpl->_data.empty();
380   }
381
382   CheckAccessDeleted::size_type CheckAccessDeleted::size() const
383   {
384     return _pimpl->_data.size();
385   }
386
387   CheckAccessDeleted::const_iterator CheckAccessDeleted::begin() const
388   {
389     return _pimpl->_data.begin();
390   }
391
392   CheckAccessDeleted::const_iterator CheckAccessDeleted::end() const
393   {
394     return _pimpl->_data.end();
395   }
396
397   void CheckAccessDeleted::setDebugOutputFile(const Pathname &filename_r)
398   {
399     _pimpl->_debugFile = filename_r;
400   }
401
402   std::string CheckAccessDeleted::findService( pid_t pid_r )
403   {
404     ProcInfo p;
405     p.pid = str::numstring( pid_r );
406     return p.service();
407   }
408
409   std::string CheckAccessDeleted::ProcInfo::service() const
410   {
411     static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
412     str::smatch what;
413     std::string ret;
414     iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
415                             [&]( int num_r, std::string line_r )->bool
416                             {
417                               if ( str::regex_match( line_r, what, rx ) )
418                               {
419                                 ret = what[2];
420                                 return false;   // stop after match
421                               }
422                               return true;
423                             } );
424     return ret;
425   }
426
427   /******************************************************************
428   **
429   **    FUNCTION NAME : operator<<
430   **    FUNCTION TYPE : std::ostream &
431   */
432   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
433   {
434     return dumpRange( str << "CheckAccessDeleted ",
435                       obj.begin(),
436                       obj.end() );
437   }
438
439    /******************************************************************
440   **
441   **    FUNCTION NAME : operator<<
442   **    FUNCTION TYPE : std::ostream &
443   */
444   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
445   {
446     if ( obj.pid.empty() )
447       return str << "<NoProc>";
448
449     return dumpRangeLine( str << obj.command
450                               << '<' << obj.pid
451                               << '|' << obj.ppid
452                               << '|' << obj.puid
453                               << '|' << obj.login
454                               << '>',
455                           obj.files.begin(),
456                           obj.files.end() );
457   }
458
459  /////////////////////////////////////////////////////////////////
460 } // namespace zypp
461 ///////////////////////////////////////////////////////////////////