libzypp 17.35.16
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
263 if (!_fromLsofFileMode)
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 for_( ch, line_r.c_str(), ch+line_r.size() )
299 {
300 switch ( *ch )
301 {
302 case 'k':
303 if ( *(ch+1) != '0' ) // skip non-zero link counts
304 return;
305 break;
306 case 'f':
307 f = ch+1;
308 break;
309 case 't':
310 t = ch+1;
311 break;
312 case 'n':
313 n = ch+1;
314 break;
315 }
316 if ( *ch == '\n' ) break; // end of data
317 do { ++ch; } while ( *ch != '\0' ); // skip to next field
318 }
319
320 if ( !t || !f || !n )
321 return; // wrong filedescriptor/type/name
322
323 if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
324 || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
325 return; // wrong type
326
327 if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
328 || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
329 || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
330 || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
331 return; // wrong filedescriptor type
332
333 if ( str::contains( n, "(stat: Permission denied)" ) )
334 return; // Avoid reporting false positive due to insufficient permission.
335
336 if ( ! _verbose )
337 {
338 if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
339 return; // Try to avoid reporting false positive unless verbose.
340 }
341
342 if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
343 {
344 static const char * black[] = {
345 "/SYSV"
346 , "/var/"
347 , "/dev/"
348 , "/tmp/"
349 , "/proc/"
350 , "/memfd:"
351 , "/snap/"
352 };
353 for_( it, arrayBegin( black ), arrayEnd( black ) )
354 {
355 if ( str::hasPrefix( n, *it ) )
356 return;
357 }
358 }
359 // Add if no duplicate
360 if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
361 debMap->push_back(line_r);
362 }
363 cache_r.second.insert( n );
364 }
365
367 : _pimpl(new Impl)
368 {
369 if ( doCheck_r ) check();
370 }
371
373 {
374 _pimpl->_verbose = verbose_r;
376
377 FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
378 if ( !inFile ) {
379 ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
380 }
381
382 //inFile is closed by ExternalDataSource
383 externalprogram::ExternalDataSource inSource( inFile, nullptr );
384 auto cache = _pimpl->filterInput( inSource );
385 return _pimpl->createProcInfo( cache );
386 }
387
389 {
390 // cachemap: PID => (deleted files)
391 // NOTE: omit PIDs running in a (lxc/docker) container
392 std::map<pid_t,CacheEntry> cachemap;
393
394 bool debugEnabled = !_debugFile.empty();
395
396 pid_t cachepid = 0;
397 FilterRunsInContainer runsInLXC;
398 MIL << "Silently scanning lsof output..." << endl;
399 zypp::base::LogControl::TmpLineWriter shutUp; // suppress excessive readdir etc. logging in runsInLXC
400 for( std::string line = source.receiveLine( 30 * 1000 ); ! line.empty(); line = source.receiveLine( 30 * 1000 ) )
401 {
402 // NOTE: line contains '\0' separeated fields!
403 if ( line[0] == 'p' )
404 {
405 str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
406 if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
407 if ( debugEnabled ) {
408 auto &pidMad = debugMap[cachepid];
409 if ( pidMad.empty() )
410 debugMap[cachepid].push_back( line );
411 else
412 debugMap[cachepid].front() = line;
413 }
414 cachemap[cachepid].first.swap( line );
415 } else {
416 cachepid = 0; // ignore this pid
417 }
418 }
419 else if ( cachepid )
420 {
421 auto &dbgMap = debugMap[cachepid];
422 addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
423 }
424 }
425 return cachemap;
426 }
427
429 {
430 static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
431 if ( lsofNoOptKi() )
432 argv[3] = NULL;
433
434 _pimpl->_verbose = verbose_r;
435 _pimpl->_fromLsofFileMode = false;
436
438 std::map<pid_t,CacheEntry> cachemap;
439
440 try {
441 cachemap = _pimpl->filterInput( prog );
442 } catch ( const io::TimeoutException &e ) {
443 ZYPP_CAUGHT( e );
444 prog.kill();
445 ZYPP_THROW ( Exception( "Reading data from 'lsof' timed out.") );
446 }
447
448 int ret = prog.close();
449 if ( ret != 0 )
450 {
451 if ( ret == 129 )
452 {
453 ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
454 }
455 Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
456 err.remember( prog.execError() );
457 ZYPP_THROW( err );
458 }
459
460 return _pimpl->createProcInfo( cachemap );
461 }
462
464 {
465 std::ofstream debugFileOut;
466 bool debugEnabled = false;
467 if ( !_debugFile.empty() ) {
468 debugFileOut.open( _debugFile.c_str() );
469 debugEnabled = debugFileOut.is_open();
470
471 if ( !debugEnabled ) {
472 ERR<<"Unable to open debug file: "<<_debugFile<<endl;
473 }
474 }
475
476 _data.clear();
477 for ( const auto &cached : in )
478 {
479 if (!debugEnabled)
480 addDataIf( cached.second);
481 else {
482 std::vector<std::string> *mapPtr = nullptr;
483
484 auto dbgInfo = debugMap.find(cached.first);
485 if ( dbgInfo != debugMap.end() )
486 mapPtr = &(dbgInfo->second);
487
488 if( !addDataIf( cached.second, mapPtr ) )
489 continue;
490
491 for ( const std::string &dbgLine: dbgInfo->second ) {
492 debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
493 }
494 }
495 }
496 return _data.size();
497 }
498
500 {
501 return _pimpl->_data.empty();
502 }
503
508
513
518
520 {
521 _pimpl->_debugFile = filename_r;
522 }
523
524 std::string CheckAccessDeleted::findService( pid_t pid_r )
525 {
526 ProcInfo p;
527 p.pid = str::numstring( pid_r );
528 return p.service();
529 }
530
532 {
533 // cgroup entries like:
534 // 1:name=systemd:/system.slice/systemd-udevd.service
535 // 0::/system.slice/systemd-udevd.service
536 // 0::/system.slice/systemd-udevd.service/udev
537 static const str::regex rx( "(0::|[0-9]+:name=systemd:)/system.slice/(.*/)?(.*).service(/.*)?$" );
538 str::smatch what;
539 std::string ret;
540 iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
541 [&]( int num_r, const std::string& line_r )->bool
542 {
543 if ( str::regex_match( line_r, what, rx ) )
544 {
545 ret = what[3];
546 return false; // stop after match
547 }
548 return true;
549 } );
550 return ret;
551 }
552
553 /******************************************************************
554 **
555 ** FUNCTION NAME : operator<<
556 ** FUNCTION TYPE : std::ostream &
557 */
558 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
559 {
560 return dumpRange( str << "CheckAccessDeleted ",
561 obj.begin(),
562 obj.end() );
563 }
564
565 /******************************************************************
566 **
567 ** FUNCTION NAME : operator<<
568 ** FUNCTION TYPE : std::ostream &
569 */
570 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
571 {
572 if ( obj.pid.empty() )
573 return str << "<NoProc>";
574
575 return dumpRangeLine( str << obj.command
576 << '<' << obj.pid
577 << '|' << obj.ppid
578 << '|' << obj.puid
579 << '|' << obj.login
580 << '>',
581 obj.files.begin(),
582 obj.files.end() );
583 }
584
586} // 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
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:100
#define ERR
Definition Logger.h:102
Interface to gettext.