libzypp 17.37.17
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 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
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
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
bool operator()(const zypp::Arch &lhs, const zypp::Arch &rhs) const
Default order for std::container based Arch::compare.
Definition Arch.h:370
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
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:153
void remember(const Exception &old_r)
Store an other Exception as history.
Definition Exception.cc:154
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
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:290
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition String.h:1097
bool startsWith(const C_Str &str_r, const C_Str &prefix_r)
alias for hasPrefix
Definition String.h:1155
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:1061
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:254
#define arrayBegin(A)
Simple C-array iterator.
Definition Easy.h:36
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition Easy.h:27
#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:475
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:459
#define _(MSG)
Definition Gettext.h:39
#define MIL
Definition Logger.h:100
#define ERR
Definition Logger.h:102
Interface to gettext.