libzypp 17.35.9
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 // 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
392 {
393 _pimpl->_verbose = verbose_r;
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
527
532
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
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:141
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 startsWith(const C_Str &str_r, const C_Str &prefix_r)
alias for hasPrefix
Definition String.h:1084
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:440
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:424
#define _(MSG)
Definition Gettext.h:39
#define MIL
Definition Logger.h:98
#define ERR
Definition Logger.h:100
Interface to gettext.