libzypp 17.34.1
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>
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>
25#include <zypp/base/Regex.h>
26#include <zypp/base/IOStream.h>
27#include <zypp-core/base/InputStream>
29
31
32using std::endl;
33
34#undef ZYPP_BASE_LOGGER_LOGGROUP
35#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
36
38namespace 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 // get stat info for the target file
97 const PathInfo linkStat( linkTarget );
98
99 // Non-existent path means it's not reachable by us.
100 if ( !linkStat.isExist() )
101 return CONTAINER;
102
103 // If the file exists, it could simply mean it exists in and outside a container, check inode to be safe
104 if ( linkStat.ino() != procInfoStat.ino())
105 return CONTAINER;
106
107 // 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
108 if ( linkStat.dev() != procInfoStat.dev() )
109 return CONTAINER;
110
111 // assume HOST if all tests fail
112 return HOST;
113 }
114
115 public:
116
120 bool operator()( const pid_t pid ) const {
121
122 // first check the exe file
123 const Pathname pidDir = Pathname("/proc") / asString(pid);
124 const Pathname exeFile = pidDir / "exe";
125
126 auto res = in_our_root( exeFile );
127 if ( res > IGNORE )
128 return res == CONTAINER;
129
130 // if IGNORE was returned we need to continue testing all the files in /proc/<pid>/map_files until we hopefully
131 // find a still existing file. If all tests fail we will simply assume this pid is running on the HOST
132
133 // 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
134 std::unordered_set<std::string> tested;
135
136 // iterate over all the entries in /proc/<pid>/map_files
137 filesystem::dirForEach( pidDir / "map_files", [ this, &tested, &res ]( const Pathname & dir_r, const char *const & name_r ){
138
139 // some helpers to make the code more self explanatory
140 constexpr bool contloop = true;
141 constexpr bool stoploop = false;
142
143 const Pathname entryName = dir_r / name_r;
144
145 // get the links target file and check if we alreadys know it, also if we can not read link information we skip the file
146 const Pathname linkTarget = filesystem::readlink( entryName );
147 if ( linkTarget.empty() || !tested.insert( linkTarget.asString() ).second ) return contloop;
148
149 // try to get file type
150 const auto mappedFileType = in_our_root( entryName );
151
152 // if we got something, remember the value and stop the loop
153 if ( mappedFileType > IGNORE ) {
154 res = mappedFileType;
155 return stoploop;
156 }
157 return contloop;
158 });
159
160 // If res is still IGNORE, we did not find a explicit answer. So, to be safe, we assume it is running on the host.
161 if ( res == IGNORE )
162 return false; // can't tell for sure, lets assume host
163
164 return res == CONTAINER;
165 }
166
167 FilterRunsInContainer() {}
168 };
169
170
176 bool lsofNoOptKi()
177 {
178 using target::rpm::librpmDb;
179 // RpmDb access is blocked while the Target is not initialized.
180 // Launching the Target just for this query would be an overkill.
181 struct TmpUnblock {
182 TmpUnblock()
183 : _wasBlocked( librpmDb::isBlocked() )
184 { if ( _wasBlocked ) librpmDb::unblockAccess(); }
185 TmpUnblock(const TmpUnblock &) = delete;
186 TmpUnblock(TmpUnblock &&) = delete;
187 TmpUnblock &operator=(const TmpUnblock &) = delete;
188 TmpUnblock &operator=(TmpUnblock &&) = delete;
189 ~TmpUnblock() {
190 if (_wasBlocked)
191 librpmDb::blockAccess();
192 }
193
194 private:
195 bool _wasBlocked;
196 } tmpUnblock;
197
198 librpmDb::db_const_iterator it;
199 return( it.findPackage( "lsof" ) && it->tag_edition() < Edition("4.90") && !it->tag_provides().count( Capability("backported-option-Ki") ) );
200 }
201
202 } //namespace
204
206 {
207 public:
209
210 bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
211 void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
212
213 std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
214 CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
215
216 std::vector<CheckAccessDeleted::ProcInfo> _data;
217 bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
218 bool _verbose = false;
219
220 std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
222 };
223
225 {
226 Impl *myClone = new Impl( *this );
227 return myClone;
228 }
229
234 inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
235 {
236 const auto & filelist( cache_r.second );
237
238 if ( filelist.empty() )
239 return false;
240
241 // at least one file access so keep it:
242 _data.push_back( CheckAccessDeleted::ProcInfo() );
243 CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
244 pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
245
246 const std::string & pline( cache_r.first );
247 std::string commandname; // pinfo.command if still needed...
248 std::ostringstream pLineStr; //rewrite the first line in debug cache
249 for_( ch, pline.begin(), pline.end() )
250 {
251 switch ( *ch )
252 {
253 case 'p':
254 pinfo.pid = &*(ch+1);
255 if ( debMap )
256 pLineStr <<&*(ch)<<'\0';
257 break;
258 case 'R':
259 pinfo.ppid = &*(ch+1);
260 if ( debMap )
261 pLineStr <<&*(ch)<<'\0';
262 break;
263 case 'u':
264 pinfo.puid = &*(ch+1);
265 if ( debMap )
266 pLineStr <<&*(ch)<<'\0';
267 break;
268 case 'L':
269 pinfo.login = &*(ch+1);
270 if ( debMap )
271 pLineStr <<&*(ch)<<'\0';
272 break;
273 case 'c':
274 if ( pinfo.command.empty() ) {
275 commandname = &*(ch+1);
276 // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
277 if (!_fromLsofFileMode)
278 pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
279 if ( pinfo.command.empty() )
280 pinfo.command = std::move(commandname);
281 if ( debMap )
282 pLineStr <<'c'<<pinfo.command<<'\0';
283 }
284 break;
285 }
286 if ( *ch == '\n' ) break; // end of data
287 do { ++ch; } while ( *ch != '\0' ); // skip to next field
288 }
289
290 //replace the data in the debug cache as well
291 if ( debMap ) {
292 pLineStr<<endl;
293 debMap->front() = pLineStr.str();
294 }
295
296 //entry was added
297 return true;
298 }
299
300
306 inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
307 {
308 const char * f = 0;
309 const char * t = 0;
310 const char * n = 0;
311
312 for_( ch, line_r.c_str(), ch+line_r.size() )
313 {
314 switch ( *ch )
315 {
316 case 'k':
317 if ( *(ch+1) != '0' ) // skip non-zero link counts
318 return;
319 break;
320 case 'f':
321 f = ch+1;
322 break;
323 case 't':
324 t = ch+1;
325 break;
326 case 'n':
327 n = ch+1;
328 break;
329 }
330 if ( *ch == '\n' ) break; // end of data
331 do { ++ch; } while ( *ch != '\0' ); // skip to next field
332 }
333
334 if ( !t || !f || !n )
335 return; // wrong filedescriptor/type/name
336
337 if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
338 || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
339 return; // wrong type
340
341 if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
342 || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
343 || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
344 || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
345 return; // wrong filedescriptor type
346
347 if ( str::contains( n, "(stat: Permission denied)" ) )
348 return; // Avoid reporting false positive due to insufficient permission.
349
350 if ( ! _verbose )
351 {
352 if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
353 return; // Try to avoid reporting false positive unless verbose.
354 }
355
356 if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
357 {
358 static const char * black[] = {
359 "/SYSV"
360 , "/var/"
361 , "/dev/"
362 , "/tmp/"
363 , "/proc/"
364 , "/memfd:"
365 };
366 for_( it, arrayBegin( black ), arrayEnd( black ) )
367 {
368 if ( str::hasPrefix( n, *it ) )
369 return;
370 }
371 }
372 // Add if no duplicate
373 if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
374 debMap->push_back(line_r);
375 }
376 cache_r.second.insert( n );
377 }
378
380 : _pimpl(new Impl)
381 {
382 if ( doCheck_r ) check();
383 }
384
386 {
387 _pimpl->_verbose = verbose_r;
389
390 FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
391 if ( !inFile ) {
392 ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
393 }
394
395 //inFile is closed by ExternalDataSource
396 externalprogram::ExternalDataSource inSource( inFile, nullptr );
397 auto cache = _pimpl->filterInput( inSource );
398 return _pimpl->createProcInfo( cache );
399 }
400
402 {
403 // cachemap: PID => (deleted files)
404 // NOTE: omit PIDs running in a (lxc/docker) container
405 std::map<pid_t,CacheEntry> cachemap;
406
407 bool debugEnabled = !_debugFile.empty();
408
409 pid_t cachepid = 0;
410 FilterRunsInContainer runsInLXC;
411 MIL << "Silently scanning lsof output..." << endl;
412 zypp::base::LogControl::TmpLineWriter shutUp; // suppress excessive readdir etc. logging in runsInLXC
413 for( std::string line = source.receiveLine( 30 * 1000 ); ! line.empty(); line = source.receiveLine( 30 * 1000 ) )
414 {
415 // NOTE: line contains '\0' separeated fields!
416 if ( line[0] == 'p' )
417 {
418 str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
419 if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
420 if ( debugEnabled ) {
421 auto &pidMad = debugMap[cachepid];
422 if ( pidMad.empty() )
423 debugMap[cachepid].push_back( line );
424 else
425 debugMap[cachepid].front() = line;
426 }
427 cachemap[cachepid].first.swap( line );
428 } else {
429 cachepid = 0; // ignore this pid
430 }
431 }
432 else if ( cachepid )
433 {
434 auto &dbgMap = debugMap[cachepid];
435 addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
436 }
437 }
438 return cachemap;
439 }
440
442 {
443 static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
444 if ( lsofNoOptKi() )
445 argv[3] = NULL;
446
447 _pimpl->_verbose = verbose_r;
448 _pimpl->_fromLsofFileMode = false;
449
451 std::map<pid_t,CacheEntry> cachemap;
452
453 try {
454 cachemap = _pimpl->filterInput( prog );
455 } catch ( const io::TimeoutException &e ) {
456 ZYPP_CAUGHT( e );
457 prog.kill();
458 ZYPP_THROW ( Exception( "Reading data from 'lsof' timed out.") );
459 }
460
461 int ret = prog.close();
462 if ( ret != 0 )
463 {
464 if ( ret == 129 )
465 {
466 ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
467 }
468 Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
469 err.remember( prog.execError() );
470 ZYPP_THROW( err );
471 }
472
473 return _pimpl->createProcInfo( cachemap );
474 }
475
477 {
478 std::ofstream debugFileOut;
479 bool debugEnabled = false;
480 if ( !_debugFile.empty() ) {
481 debugFileOut.open( _debugFile.c_str() );
482 debugEnabled = debugFileOut.is_open();
483
484 if ( !debugEnabled ) {
485 ERR<<"Unable to open debug file: "<<_debugFile<<endl;
486 }
487 }
488
489 _data.clear();
490 for ( const auto &cached : in )
491 {
492 if (!debugEnabled)
493 addDataIf( cached.second);
494 else {
495 std::vector<std::string> *mapPtr = nullptr;
496
497 auto dbgInfo = debugMap.find(cached.first);
498 if ( dbgInfo != debugMap.end() )
499 mapPtr = &(dbgInfo->second);
500
501 if( !addDataIf( cached.second, mapPtr ) )
502 continue;
503
504 for ( const std::string &dbgLine: dbgInfo->second ) {
505 debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
506 }
507 }
508 }
509 return _data.size();
510 }
511
513 {
514 return _pimpl->_data.empty();
515 }
516
521
526
531
533 {
534 _pimpl->_debugFile = filename_r;
535 }
536
537 std::string CheckAccessDeleted::findService( pid_t pid_r )
538 {
539 ProcInfo p;
540 p.pid = str::numstring( pid_r );
541 return p.service();
542 }
543
545 {
546 // cgroup entries like:
547 // 1:name=systemd:/system.slice/systemd-udevd.service
548 // 0::/system.slice/systemd-udevd.service
549 // 0::/system.slice/systemd-udevd.service/udev
550 static const str::regex rx( "(0::|[0-9]+:name=systemd:)/system.slice/(.*/)?(.*).service(/.*)?$" );
551 str::smatch what;
552 std::string ret;
553 iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
554 [&]( int num_r, const std::string& line_r )->bool
555 {
556 if ( str::regex_match( line_r, what, rx ) )
557 {
558 ret = what[3];
559 return false; // stop after match
560 }
561 return true;
562 } );
563 return ret;
564 }
565
566 /******************************************************************
567 **
568 ** FUNCTION NAME : operator<<
569 ** FUNCTION TYPE : std::ostream &
570 */
571 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
572 {
573 return dumpRange( str << "CheckAccessDeleted ",
574 obj.begin(),
575 obj.end() );
576 }
577
578 /******************************************************************
579 **
580 ** FUNCTION NAME : operator<<
581 ** FUNCTION TYPE : std::ostream &
582 */
583 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
584 {
585 if ( obj.pid.empty() )
586 return str << "<NoProc>";
587
588 return dumpRangeLine( str << obj.command
589 << '<' << obj.pid
590 << '|' << obj.ppid
591 << '|' << obj.puid
592 << '|' << obj.login
593 << '>',
594 obj.files.begin(),
595 obj.files.end() );
596 }
597
599} // namespace zypp
CheckAccessDeleted::Impl * clone() const
CheckAccessDeleted::size_type createProcInfo(const std::map< pid_t, CacheEntry > &in)
std::map< pid_t, std::vector< std::string > > debugMap
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::map< pid_t, CacheEntry > filterInput(externalprogram::ExternalDataSource &source)
bool addDataIf(const CacheEntry &cache_r, std::vector< std::string > *debMap=nullptr)
Add cache to data if the process is accessing deleted files.
std::vector< CheckAccessDeleted::ProcInfo > _data
Check for running processes which access deleted executables or libraries.
size_type check(bool verbose_r=false)
Check for running processes which access deleted executables or libraries.
CheckAccessDeleted(bool doCheck_r=true)
Default ctor performs check immediately.
const_iterator end() const
std::ostream & operator<<(std::ostream &str, const CheckAccessDeleted &obj)
Stream output.
const_iterator begin() const
static std::string findService(pid_t pid_r)
Guess if pid was started by a systemd service script.
std::vector< ProcInfo >::const_iterator const_iterator
void setDebugOutputFile(const Pathname &filename_r)
Writes all filtered process entries that make it into the final set into a file specified by filename...
RWCOW_pointer< Impl > _pimpl
Base class for Exception.
Definition Exception.h:147
void remember(const Exception &old_r)
Store an other Exception as history.
Definition Exception.cc:124
Execute a program and give access to its io An object of this class encapsulates the execution of an ...
int close() override
Wait for the progamm to complete.
bool kill()
Kill the program.
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
Helper to create and pass std::istream.
Definition inputstream.h:57
Bidirectional stream to external data.
std::string receiveLine()
Read one line from the input stream.
const char * c_str() const
String representation.
Definition Pathname.h:112
bool empty() const
Test for an empty path.
Definition Pathname.h:116
Regular expression.
Definition Regex.h:95
Regular expression match result.
Definition Regex.h:168
String related utilities and Regular expression matching.
int dirForEach(const Pathname &dir_r, const StrMatcher &matcher_r, function< bool(const Pathname &, const char *const)> fnc_r)
Definition PathInfo.cc:32
int readlink(const Pathname &symlink_r, Pathname &target_r)
Like 'readlink'.
Definition PathInfo.cc:929
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::string numstring(char n, int w=0)
Definition String.h:289
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition String.h:1026
bool regex_match(const std::string &s, smatch &matches, const regex &regex)
\relates regex \ingroup ZYPP_STR_REGEX \relates regex \ingroup ZYPP_STR_REGEX
Definition Regex.h:70
TInt strtonum(const C_Str &str)
Parsing numbers from string.
bool contains(const C_Str &str_r, const C_Str &val_r)
Locate substring case sensitive.
Definition String.h:990
Easy-to use interface to the ZYPP dependency resolver.
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition LogTools.h:143
std::ostream & dumpRange(std::ostream &str, TIterator begin, TIterator end, const std::string &intro="{", const std::string &pfx="\n ", const std::string &sep="\n ", const std::string &sfx="\n", const std::string &extro="}")
Print range defined by iterators (multiline style).
Definition LogTools.h:120
std::string asString(const Patch::Category &obj)
Definition Patch.cc:122
Data about one running process accessing deleted files.
std::string service() const
Guess if command was started by a systemd service script.
std::string login
process login name
std::string puid
process user ID
std::string command
process command name
std::vector< std::string > files
list of deleted executables or libraries accessed
std::string ppid
parent process ID
Exchange LineWriter for the lifetime of this object.
Definition LogControl.h:191
Convenient building of std::string with boost::format.
Definition String.h:253
#define arrayBegin(A)
Simple C-array iterator.
Definition Easy.h:41
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition Easy.h:28
#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:437
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:429
#define _(MSG)
Definition Gettext.h:39
#define MIL
Definition Logger.h:98
#define ERR
Definition Logger.h:100
Interface to gettext.