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