libzypp  17.36.3
CheckAccessDeleted.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #include <iostream>
13 #include <fstream>
14 #include <unordered_set>
15 #include <iterator>
16 #include <stdio.h>
17 #include <zypp/base/LogControl.h>
18 #include <zypp/base/LogTools.h>
19 #include <zypp/base/String.h>
20 #include <zypp/base/Gettext.h>
21 #include <zypp/base/Exception.h>
22 
23 #include <zypp/PathInfo.h>
24 #include <zypp/ExternalProgram.h>
25 #include <zypp/base/Regex.h>
26 #include <zypp/base/IOStream.h>
27 #include <zypp-core/base/InputStream>
29 
31 
32 using std::endl;
33 
34 #undef ZYPP_BASE_LOGGER_LOGGROUP
35 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
36 
38 namespace zypp
39 {
40 
42  namespace
43  {
44  //
45  // lsof output lines are a sequence of NUL terminated fields,
46  // where the 1st char determines the fields type.
47  //
48  // (pcuL) pid command userid loginname
49  // (ftkn).filedescriptor type linkcount filename
50  //
52 
54  using CacheEntry = std::pair<std::string, std::unordered_set<std::string>>;
55 
62  struct FilterRunsInContainer
63  {
64  private:
65 
66  enum Type {
67  IGNORE,
68  HOST,
69  CONTAINER
70  };
71 
77  Type in_our_root( const Pathname &path ) const {
78 
79  const PathInfo procInfoStat( path );
80 
81  // if we can not stat the file continue to the next one
82  if ( procInfoStat.error() ) return IGNORE;
83 
84  // if the file was unlinked ignore it
85  if ( procInfoStat.nlink() == 0 )
86  return IGNORE;
87 
88  // get the file the link points to, if that fails continue to the next
89  const Pathname linkTarget = filesystem::readlink( path );
90  if ( linkTarget.empty() ) return IGNORE;
91 
92  // Pipe or socket 'type:[inode]' or an 'anon_inode:<file-type>'
93  // They may or may not belong to a container... (bsc#1218291)
94  if ( linkTarget.relative() ) return IGNORE;
95 
96  // bsc#1226014. Ignore snaps. Execuables below /snap/
97  // (may also be detectable via /proc/PID/cgroup)
98  if ( str::startsWith( linkTarget.asString(), "/snap/" ) )
99  return CONTAINER;
100 
101  // get stat info for the target file
102  const PathInfo linkStat( linkTarget );
103 
104  // Non-existent path means it's not reachable by us.
105  if ( !linkStat.isExist() )
106  return CONTAINER;
107 
108  // If the file exists, it could simply mean it exists in and outside a container, check inode to be safe
109  if ( linkStat.ino() != procInfoStat.ino())
110  return CONTAINER;
111 
112  // If the inode is the same, it could simply mean it exists in and outside a container but on different devices, check to be safe
113  if ( linkStat.dev() != procInfoStat.dev() )
114  return CONTAINER;
115 
116  // assume HOST if all tests fail
117  return HOST;
118  }
119 
120  public:
121 
125  bool operator()( const pid_t pid ) const {
126 
127  // first check the exe file
128  const Pathname pidDir = Pathname("/proc") / asString(pid);
129  const Pathname exeFile = pidDir / "exe";
130 
131  auto res = in_our_root( exeFile );
132  if ( res > IGNORE )
133  return res == CONTAINER;
134 
135  // if IGNORE was returned we need to continue testing all the files in /proc/<pid>/map_files until we hopefully
136  // find a still existing file. If all tests fail we will simply assume this pid is running on the HOST
137 
138  // a map of all already tested files, each file can be mapped multiple times and we do not want to check them more than once
139  std::unordered_set<std::string> tested;
140 
141  // iterate over all the entries in /proc/<pid>/map_files
142  filesystem::dirForEach( pidDir / "map_files", [ this, &tested, &res ]( const Pathname & dir_r, const char *const & name_r ){
143 
144  // some helpers to make the code more self explanatory
145  constexpr bool contloop = true;
146  constexpr bool stoploop = false;
147 
148  const Pathname entryName = dir_r / name_r;
149 
150  // get the links target file and check if we alreadys know it, also if we can not read link information we skip the file
151  const Pathname linkTarget = filesystem::readlink( entryName );
152  if ( linkTarget.empty() || !tested.insert( linkTarget.asString() ).second ) return contloop;
153 
154  // try to get file type
155  const auto mappedFileType = in_our_root( entryName );
156 
157  // if we got something, remember the value and stop the loop
158  if ( mappedFileType > IGNORE ) {
159  res = mappedFileType;
160  return stoploop;
161  }
162  return contloop;
163  });
164 
165  // If res is still IGNORE, we did not find a explicit answer. So, to be safe, we assume it is running on the host.
166  if ( res == IGNORE )
167  return false; // can't tell for sure, lets assume host
168 
169  return res == CONTAINER;
170  }
171 
172  FilterRunsInContainer() {}
173  };
174 
175 
181  bool lsofNoOptKi()
182  {
183  using target::rpm::librpmDb;
184  librpmDb::db_const_iterator it( "/" );
185  return( it.findPackage( "lsof" ) && it->tag_edition() < Edition("4.90") && !it->tag_provides().count( Capability("backported-option-Ki") ) );
186  }
187 
188  } //namespace
190 
192  {
193  public:
195 
196  bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
197  void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
198 
199  std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
200  CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
201 
202  std::vector<CheckAccessDeleted::ProcInfo> _data;
203  bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
204  bool _verbose = false;
205 
206  std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
208  };
209 
211  {
212  Impl *myClone = new Impl( *this );
213  return myClone;
214  }
215 
220  inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
221  {
222  const auto & filelist( cache_r.second );
223 
224  if ( filelist.empty() )
225  return false;
226 
227  // at least one file access so keep it:
228  _data.push_back( CheckAccessDeleted::ProcInfo() );
229  CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
230  pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
231 
232  const std::string & pline( cache_r.first );
233  std::string commandname; // pinfo.command if still needed...
234  std::ostringstream pLineStr; //rewrite the first line in debug cache
235  for_( ch, pline.begin(), pline.end() )
236  {
237  switch ( *ch )
238  {
239  case 'p':
240  pinfo.pid = &*(ch+1);
241  if ( debMap )
242  pLineStr <<&*(ch)<<'\0';
243  break;
244  case 'R':
245  pinfo.ppid = &*(ch+1);
246  if ( debMap )
247  pLineStr <<&*(ch)<<'\0';
248  break;
249  case 'u':
250  pinfo.puid = &*(ch+1);
251  if ( debMap )
252  pLineStr <<&*(ch)<<'\0';
253  break;
254  case 'L':
255  pinfo.login = &*(ch+1);
256  if ( debMap )
257  pLineStr <<&*(ch)<<'\0';
258  break;
259  case 'c':
260  if ( pinfo.command.empty() ) {
261  commandname = &*(ch+1);
262  // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
263  if (!_fromLsofFileMode)
264  pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
265  if ( pinfo.command.empty() )
266  pinfo.command = std::move(commandname);
267  if ( debMap )
268  pLineStr <<'c'<<pinfo.command<<'\0';
269  }
270  break;
271  }
272  if ( *ch == '\n' ) break; // end of data
273  do { ++ch; } while ( *ch != '\0' ); // skip to next field
274  }
275 
276  //replace the data in the debug cache as well
277  if ( debMap ) {
278  pLineStr<<endl;
279  debMap->front() = pLineStr.str();
280  }
281 
282  //entry was added
283  return true;
284  }
285 
286 
292  inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
293  {
294  const char * f = 0;
295  const char * t = 0;
296  const char * n = 0;
297 
298  // match any stat error appended to file path
299  static const str::regex statErr(R"(.*\(stat: [^)]+\)$)");
300 
301  for_( ch, line_r.c_str(), ch+line_r.size() )
302  {
303  switch ( *ch )
304  {
305  case 'k':
306  if ( *(ch+1) != '0' ) // skip non-zero link counts
307  return;
308  break;
309  case 'f':
310  f = ch+1;
311  break;
312  case 't':
313  t = ch+1;
314  break;
315  case 'n':
316  n = ch+1;
317  break;
318  }
319  if ( *ch == '\n' ) break; // end of data
320  do { ++ch; } while ( *ch != '\0' ); // skip to next field
321  }
322 
323  if ( !t || !f || !n )
324  return; // wrong filedescriptor/type/name
325 
326  if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
327  || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
328  return; // wrong type
329 
330  if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
331  || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
332  || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
333  || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
334  return; // wrong filedescriptor type
335 
336  // lib/dialects/linux/dproc.c will append stat error to file path
337  // if the file has not been detected as deleted. Ignore those cases
338  if ( str::regex_match( n, statErr ) )
339  return; // Avoid reporting false positive due to insufficient permission.
340 
341  if ( ! _verbose )
342  {
343  if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
344  return; // Try to avoid reporting false positive unless verbose.
345  }
346 
347  if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
348  {
349  static const char * black[] = {
350  "/SYSV"
351  , "/var/"
352  , "/dev/"
353  , "/tmp/"
354  , "/proc/"
355  , "/memfd:"
356  , "/snap/"
357  };
358  for_( it, arrayBegin( black ), arrayEnd( black ) )
359  {
360  if ( str::hasPrefix( n, *it ) )
361  return;
362  }
363  }
364  // Add if no duplicate
365  if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
366  debMap->push_back(line_r);
367  }
368  cache_r.second.insert( n );
369  }
370 
372  : _pimpl(new Impl)
373  {
374  if ( doCheck_r ) check();
375  }
376 
377  CheckAccessDeleted::size_type CheckAccessDeleted::check( const Pathname &lsofOutput_r, bool verbose_r )
378  {
379  _pimpl->_verbose = verbose_r;
380  _pimpl->_fromLsofFileMode = true;
381 
382  FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
383  if ( !inFile ) {
384  ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
385  }
386 
387  //inFile is closed by ExternalDataSource
388  externalprogram::ExternalDataSource inSource( inFile, nullptr );
389  auto cache = _pimpl->filterInput( inSource );
390  return _pimpl->createProcInfo( cache );
391  }
392 
394  {
395  // cachemap: PID => (deleted files)
396  // NOTE: omit PIDs running in a (lxc/docker) container
397  std::map<pid_t,CacheEntry> cachemap;
398 
399  bool debugEnabled = !_debugFile.empty();
400 
401  pid_t cachepid = 0;
402  FilterRunsInContainer runsInLXC;
403  MIL << "Silently scanning lsof output..." << endl;
404  zypp::base::LogControl::TmpLineWriter shutUp; // suppress excessive readdir etc. logging in runsInLXC
405  for( std::string line = source.receiveLine( 30 * 1000 ); ! line.empty(); line = source.receiveLine( 30 * 1000 ) )
406  {
407  // NOTE: line contains '\0' separeated fields!
408  if ( line[0] == 'p' )
409  {
410  str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
411  if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
412  if ( debugEnabled ) {
413  auto &pidMad = debugMap[cachepid];
414  if ( pidMad.empty() )
415  debugMap[cachepid].push_back( line );
416  else
417  debugMap[cachepid].front() = line;
418  }
419  cachemap[cachepid].first.swap( line );
420  } else {
421  cachepid = 0; // ignore this pid
422  }
423  }
424  else if ( cachepid )
425  {
426  auto &dbgMap = debugMap[cachepid];
427  addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
428  }
429  }
430  return cachemap;
431  }
432 
434  {
435  static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
436  if ( lsofNoOptKi() )
437  argv[3] = NULL;
438 
439  _pimpl->_verbose = verbose_r;
440  _pimpl->_fromLsofFileMode = false;
441 
443  std::map<pid_t,CacheEntry> cachemap;
444 
445  try {
446  cachemap = _pimpl->filterInput( prog );
447  } catch ( const io::TimeoutException &e ) {
448  ZYPP_CAUGHT( e );
449  prog.kill();
450  ZYPP_THROW ( Exception( "Reading data from 'lsof' timed out.") );
451  }
452 
453  int ret = prog.close();
454  if ( ret != 0 )
455  {
456  if ( ret == 129 )
457  {
458  ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
459  }
460  Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
461  err.remember( prog.execError() );
462  ZYPP_THROW( err );
463  }
464 
465  return _pimpl->createProcInfo( cachemap );
466  }
467 
469  {
470  std::ofstream debugFileOut;
471  bool debugEnabled = false;
472  if ( !_debugFile.empty() ) {
473  debugFileOut.open( _debugFile.c_str() );
474  debugEnabled = debugFileOut.is_open();
475 
476  if ( !debugEnabled ) {
477  ERR<<"Unable to open debug file: "<<_debugFile<<endl;
478  }
479  }
480 
481  _data.clear();
482  for ( const auto &cached : in )
483  {
484  if (!debugEnabled)
485  addDataIf( cached.second);
486  else {
487  std::vector<std::string> *mapPtr = nullptr;
488 
489  auto dbgInfo = debugMap.find(cached.first);
490  if ( dbgInfo != debugMap.end() )
491  mapPtr = &(dbgInfo->second);
492 
493  if( !addDataIf( cached.second, mapPtr ) )
494  continue;
495 
496  for ( const std::string &dbgLine: dbgInfo->second ) {
497  debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
498  }
499  }
500  }
501  return _data.size();
502  }
503 
505  {
506  return _pimpl->_data.empty();
507  }
508 
510  {
511  return _pimpl->_data.size();
512  }
513 
515  {
516  return _pimpl->_data.begin();
517  }
518 
520  {
521  return _pimpl->_data.end();
522  }
523 
525  {
526  _pimpl->_debugFile = filename_r;
527  }
528 
529  std::string CheckAccessDeleted::findService( pid_t pid_r )
530  {
531  ProcInfo p;
532  p.pid = str::numstring( pid_r );
533  return p.service();
534  }
535 
537  {
538  // cgroup entries like:
539  // 1:name=systemd:/system.slice/systemd-udevd.service
540  // 0::/system.slice/systemd-udevd.service
541  // 0::/system.slice/systemd-udevd.service/udev
542  static const str::regex rx( "(0::|[0-9]+:name=systemd:)/system.slice/(.*/)?(.*).service(/.*)?$" );
543  str::smatch what;
544  std::string ret;
545  iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
546  [&]( int num_r, const std::string& line_r )->bool
547  {
548  if ( str::regex_match( line_r, what, rx ) )
549  {
550  ret = what[3];
551  return false; // stop after match
552  }
553  return true;
554  } );
555  return ret;
556  }
557 
558  /******************************************************************
559  **
560  ** FUNCTION NAME : operator<<
561  ** FUNCTION TYPE : std::ostream &
562  */
563  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
564  {
565  return dumpRange( str << "CheckAccessDeleted ",
566  obj.begin(),
567  obj.end() );
568  }
569 
570  /******************************************************************
571  **
572  ** FUNCTION NAME : operator<<
573  ** FUNCTION TYPE : std::ostream &
574  */
575  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
576  {
577  if ( obj.pid.empty() )
578  return str << "<NoProc>";
579 
580  return dumpRangeLine( str << obj.command
581  << '<' << obj.pid
582  << '|' << obj.ppid
583  << '|' << obj.puid
584  << '|' << obj.login
585  << '>',
586  obj.files.begin(),
587  obj.files.end() );
588  }
589 
591 } // namespace zypp
std::string asString(const Patch::Category &obj)
Definition: Patch.cc:122
bool addDataIf(const CacheEntry &cache_r, std::vector< std::string > *debMap=nullptr)
Add cache to data if the process is accessing deleted files.
Data about one running process accessing deleted files.
Interface to gettext.
#define MIL
Definition: Logger.h:100
Bidirectional stream to external data.
bool contains(const C_Str &str_r, const C_Str &val_r)
Locate substring case sensitive.
Definition: String.h:991
std::map< pid_t, CacheEntry > filterInput(externalprogram::ExternalDataSource &source)
#define _(MSG)
Definition: Gettext.h:39
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:424
Regular expression.
Definition: Regex.h:94
bool kill()
Kill the program.
std::map< pid_t, std::vector< std::string > > debugMap
int readlink(const Pathname &symlink_r, Pathname &target_r)
Like &#39;readlink&#39;.
Definition: PathInfo.cc:929
void setDebugOutputFile(const Pathname &filename_r)
Writes all filtered process entries that make it into the final set into a file specified by filename...
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
std::ostream & dumpRange(std::ostream &str, TIterator begin, TIterator end, const std::string &intro="{", const std::string &pfx="\ ", const std::string &sep="\ ", const std::string &sfx="\, const std::string &extro="}")
Print range defined by iterators (multiline style).
Definition: LogTools.h:120
int dirForEach(const Pathname &dir_r, const StrMatcher &matcher_r, function< bool(const Pathname &, const char *const)> fnc_r)
Definition: PathInfo.cc:32
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:27
const char * c_str() const
String representation.
Definition: Pathname.h:112
std::string command
process command name
String related utilities and Regular expression matching.
Helper to create and pass std::istream.
Definition: inputstream.h:56
std::string receiveLine()
Read one line from the input stream.
Convenient building of std::string with boost::format.
Definition: String.h:252
Exchange LineWriter for the lifetime of this object.
Definition: LogControl.h:190
std::ostream & operator<<(std::ostream &str, const CheckAccessDeleted &obj)
#define ERR
Definition: Logger.h:102
void remember(const Exception &old_r)
Store an other Exception as history.
Definition: Exception.cc:141
CheckAccessDeleted::Impl * clone() const
bool empty() const
Test for an empty path.
Definition: Pathname.h:116
size_type check(bool verbose_r=false)
Check for running processes which access deleted executables or libraries.
RWCOW_pointer< Impl > _pimpl
const_iterator begin() const
Execute a program and give access to its io An object of this class encapsulates the execution of an ...
Subclass to retrieve rpm database content.
std::vector< CheckAccessDeleted::ProcInfo > _data
bool startsWith(const C_Str &str_r, const C_Str &prefix_r)
alias for hasPrefix
Definition: String.h:1085
int close() override
Wait for the progamm to complete.
TInt strtonum(const C_Str &str)
Parsing numbers from string.
Definition: String.h:388
std::string puid
process user ID
const_iterator end() const
std::string numstring(char n, int w=0)
Definition: String.h:289
CheckAccessDeleted::size_type createProcInfo(const std::map< pid_t, CacheEntry > &in)
#define arrayEnd(A)
Definition: Easy.h:38
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition: Exception.h:440
Regular expression match result.
Definition: Regex.h:167
constexpr std::string_view FILE("file")
int simpleParseFile(std::istream &str_r, ParseFlags flags_r, function< bool(int, std::string)> consume_r)
Simple lineparser optionally trimming and skipping comments.
Definition: IOStream.cc:124
std::vector< ProcInfo >::const_iterator const_iterator
Base class for Exception.
Definition: Exception.h:146
Check for running processes which access deleted executables or libraries.
CheckAccessDeleted(bool doCheck_r=true)
Default ctor performs check immediately.
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:143
bool regex_match(const std::string &s, smatch &matches, const regex &regex)
regex ZYPP_STR_REGEX regex ZYPP_STR_REGEX
Definition: Regex.h:70
Easy-to use interface to the ZYPP dependency resolver.
Definition: Application.cc:19
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition: String.h:1027
std::string login
process login name
void addCacheIf(CacheEntry &cache_r, const std::string &line_r, std::vector< std::string > *debMap=nullptr)
Add file to cache if it refers to a deleted executable or library file:
std::vector< std::string > files
list of deleted executables or libraries accessed
#define arrayBegin(A)
Simple C-array iterator.
Definition: Easy.h:36
std::string ppid
parent process ID
std::string service() const
Guess if command was started by a systemd service script.
static std::string findService(pid_t pid_r)
Guess if pid was started by a systemd service script.