libzypp  17.35.8
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  // RpmDb access is blocked while the Target is not initialized.
185  // Launching the Target just for this query would be an overkill.
186  struct TmpUnblock {
187  TmpUnblock()
188  : _wasBlocked( librpmDb::isBlocked() )
189  { if ( _wasBlocked ) librpmDb::unblockAccess(); }
190  TmpUnblock(const TmpUnblock &) = delete;
191  TmpUnblock(TmpUnblock &&) = delete;
192  TmpUnblock &operator=(const TmpUnblock &) = delete;
193  TmpUnblock &operator=(TmpUnblock &&) = delete;
194  ~TmpUnblock() {
195  if (_wasBlocked)
196  librpmDb::blockAccess();
197  }
198 
199  private:
200  bool _wasBlocked;
201  } tmpUnblock;
202 
203  librpmDb::db_const_iterator it;
204  return( it.findPackage( "lsof" ) && it->tag_edition() < Edition("4.90") && !it->tag_provides().count( Capability("backported-option-Ki") ) );
205  }
206 
207  } //namespace
209 
211  {
212  public:
214 
215  bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
216  void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
217 
218  std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
219  CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
220 
221  std::vector<CheckAccessDeleted::ProcInfo> _data;
222  bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
223  bool _verbose = false;
224 
225  std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
227  };
228 
230  {
231  Impl *myClone = new Impl( *this );
232  return myClone;
233  }
234 
239  inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
240  {
241  const auto & filelist( cache_r.second );
242 
243  if ( filelist.empty() )
244  return false;
245 
246  // at least one file access so keep it:
247  _data.push_back( CheckAccessDeleted::ProcInfo() );
248  CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
249  pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
250 
251  const std::string & pline( cache_r.first );
252  std::string commandname; // pinfo.command if still needed...
253  std::ostringstream pLineStr; //rewrite the first line in debug cache
254  for_( ch, pline.begin(), pline.end() )
255  {
256  switch ( *ch )
257  {
258  case 'p':
259  pinfo.pid = &*(ch+1);
260  if ( debMap )
261  pLineStr <<&*(ch)<<'\0';
262  break;
263  case 'R':
264  pinfo.ppid = &*(ch+1);
265  if ( debMap )
266  pLineStr <<&*(ch)<<'\0';
267  break;
268  case 'u':
269  pinfo.puid = &*(ch+1);
270  if ( debMap )
271  pLineStr <<&*(ch)<<'\0';
272  break;
273  case 'L':
274  pinfo.login = &*(ch+1);
275  if ( debMap )
276  pLineStr <<&*(ch)<<'\0';
277  break;
278  case 'c':
279  if ( pinfo.command.empty() ) {
280  commandname = &*(ch+1);
281  // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
282  if (!_fromLsofFileMode)
283  pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
284  if ( pinfo.command.empty() )
285  pinfo.command = std::move(commandname);
286  if ( debMap )
287  pLineStr <<'c'<<pinfo.command<<'\0';
288  }
289  break;
290  }
291  if ( *ch == '\n' ) break; // end of data
292  do { ++ch; } while ( *ch != '\0' ); // skip to next field
293  }
294 
295  //replace the data in the debug cache as well
296  if ( debMap ) {
297  pLineStr<<endl;
298  debMap->front() = pLineStr.str();
299  }
300 
301  //entry was added
302  return true;
303  }
304 
305 
311  inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
312  {
313  const char * f = 0;
314  const char * t = 0;
315  const char * n = 0;
316 
317  for_( ch, line_r.c_str(), ch+line_r.size() )
318  {
319  switch ( *ch )
320  {
321  case 'k':
322  if ( *(ch+1) != '0' ) // skip non-zero link counts
323  return;
324  break;
325  case 'f':
326  f = ch+1;
327  break;
328  case 't':
329  t = ch+1;
330  break;
331  case 'n':
332  n = ch+1;
333  break;
334  }
335  if ( *ch == '\n' ) break; // end of data
336  do { ++ch; } while ( *ch != '\0' ); // skip to next field
337  }
338 
339  if ( !t || !f || !n )
340  return; // wrong filedescriptor/type/name
341 
342  if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
343  || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
344  return; // wrong type
345 
346  if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
347  || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
348  || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
349  || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
350  return; // wrong filedescriptor type
351 
352  if ( str::contains( n, "(stat: Permission denied)" ) )
353  return; // Avoid reporting false positive due to insufficient permission.
354 
355  if ( ! _verbose )
356  {
357  if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
358  return; // Try to avoid reporting false positive unless verbose.
359  }
360 
361  if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
362  {
363  static const char * black[] = {
364  "/SYSV"
365  , "/var/"
366  , "/dev/"
367  , "/tmp/"
368  , "/proc/"
369  , "/memfd:"
370  , "/snap/"
371  };
372  for_( it, arrayBegin( black ), arrayEnd( black ) )
373  {
374  if ( str::hasPrefix( n, *it ) )
375  return;
376  }
377  }
378  // Add if no duplicate
379  if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
380  debMap->push_back(line_r);
381  }
382  cache_r.second.insert( n );
383  }
384 
386  : _pimpl(new Impl)
387  {
388  if ( doCheck_r ) check();
389  }
390 
391  CheckAccessDeleted::size_type CheckAccessDeleted::check( const Pathname &lsofOutput_r, bool verbose_r )
392  {
393  _pimpl->_verbose = verbose_r;
394  _pimpl->_fromLsofFileMode = true;
395 
396  FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
397  if ( !inFile ) {
398  ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
399  }
400 
401  //inFile is closed by ExternalDataSource
402  externalprogram::ExternalDataSource inSource( inFile, nullptr );
403  auto cache = _pimpl->filterInput( inSource );
404  return _pimpl->createProcInfo( cache );
405  }
406 
408  {
409  // cachemap: PID => (deleted files)
410  // NOTE: omit PIDs running in a (lxc/docker) container
411  std::map<pid_t,CacheEntry> cachemap;
412 
413  bool debugEnabled = !_debugFile.empty();
414 
415  pid_t cachepid = 0;
416  FilterRunsInContainer runsInLXC;
417  MIL << "Silently scanning lsof output..." << endl;
418  zypp::base::LogControl::TmpLineWriter shutUp; // suppress excessive readdir etc. logging in runsInLXC
419  for( std::string line = source.receiveLine( 30 * 1000 ); ! line.empty(); line = source.receiveLine( 30 * 1000 ) )
420  {
421  // NOTE: line contains '\0' separeated fields!
422  if ( line[0] == 'p' )
423  {
424  str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
425  if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
426  if ( debugEnabled ) {
427  auto &pidMad = debugMap[cachepid];
428  if ( pidMad.empty() )
429  debugMap[cachepid].push_back( line );
430  else
431  debugMap[cachepid].front() = line;
432  }
433  cachemap[cachepid].first.swap( line );
434  } else {
435  cachepid = 0; // ignore this pid
436  }
437  }
438  else if ( cachepid )
439  {
440  auto &dbgMap = debugMap[cachepid];
441  addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
442  }
443  }
444  return cachemap;
445  }
446 
448  {
449  static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
450  if ( lsofNoOptKi() )
451  argv[3] = NULL;
452 
453  _pimpl->_verbose = verbose_r;
454  _pimpl->_fromLsofFileMode = false;
455 
457  std::map<pid_t,CacheEntry> cachemap;
458 
459  try {
460  cachemap = _pimpl->filterInput( prog );
461  } catch ( const io::TimeoutException &e ) {
462  ZYPP_CAUGHT( e );
463  prog.kill();
464  ZYPP_THROW ( Exception( "Reading data from 'lsof' timed out.") );
465  }
466 
467  int ret = prog.close();
468  if ( ret != 0 )
469  {
470  if ( ret == 129 )
471  {
472  ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
473  }
474  Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
475  err.remember( prog.execError() );
476  ZYPP_THROW( err );
477  }
478 
479  return _pimpl->createProcInfo( cachemap );
480  }
481 
483  {
484  std::ofstream debugFileOut;
485  bool debugEnabled = false;
486  if ( !_debugFile.empty() ) {
487  debugFileOut.open( _debugFile.c_str() );
488  debugEnabled = debugFileOut.is_open();
489 
490  if ( !debugEnabled ) {
491  ERR<<"Unable to open debug file: "<<_debugFile<<endl;
492  }
493  }
494 
495  _data.clear();
496  for ( const auto &cached : in )
497  {
498  if (!debugEnabled)
499  addDataIf( cached.second);
500  else {
501  std::vector<std::string> *mapPtr = nullptr;
502 
503  auto dbgInfo = debugMap.find(cached.first);
504  if ( dbgInfo != debugMap.end() )
505  mapPtr = &(dbgInfo->second);
506 
507  if( !addDataIf( cached.second, mapPtr ) )
508  continue;
509 
510  for ( const std::string &dbgLine: dbgInfo->second ) {
511  debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
512  }
513  }
514  }
515  return _data.size();
516  }
517 
519  {
520  return _pimpl->_data.empty();
521  }
522 
524  {
525  return _pimpl->_data.size();
526  }
527 
529  {
530  return _pimpl->_data.begin();
531  }
532 
534  {
535  return _pimpl->_data.end();
536  }
537 
539  {
540  _pimpl->_debugFile = filename_r;
541  }
542 
543  std::string CheckAccessDeleted::findService( pid_t pid_r )
544  {
545  ProcInfo p;
546  p.pid = str::numstring( pid_r );
547  return p.service();
548  }
549 
551  {
552  // cgroup entries like:
553  // 1:name=systemd:/system.slice/systemd-udevd.service
554  // 0::/system.slice/systemd-udevd.service
555  // 0::/system.slice/systemd-udevd.service/udev
556  static const str::regex rx( "(0::|[0-9]+:name=systemd:)/system.slice/(.*/)?(.*).service(/.*)?$" );
557  str::smatch what;
558  std::string ret;
559  iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
560  [&]( int num_r, const std::string& line_r )->bool
561  {
562  if ( str::regex_match( line_r, what, rx ) )
563  {
564  ret = what[3];
565  return false; // stop after match
566  }
567  return true;
568  } );
569  return ret;
570  }
571 
572  /******************************************************************
573  **
574  ** FUNCTION NAME : operator<<
575  ** FUNCTION TYPE : std::ostream &
576  */
577  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
578  {
579  return dumpRange( str << "CheckAccessDeleted ",
580  obj.begin(),
581  obj.end() );
582  }
583 
584  /******************************************************************
585  **
586  ** FUNCTION NAME : operator<<
587  ** FUNCTION TYPE : std::ostream &
588  */
589  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
590  {
591  if ( obj.pid.empty() )
592  return str << "<NoProc>";
593 
594  return dumpRangeLine( str << obj.command
595  << '<' << obj.pid
596  << '|' << obj.ppid
597  << '|' << obj.puid
598  << '|' << obj.login
599  << '>',
600  obj.files.begin(),
601  obj.files.end() );
602  }
603 
605 } // 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:98
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:28
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:100
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 ...
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:43
#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:41
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.