3131import re
3232import json
3333import struct
34+ import signal
3435import logging
3536import subprocess
3637
4445NT_GO_BUILD_ID = 4
4546IGNORED_PATHNAME = ["[heap]" , "[stack]" , "[vdso]" , "[vsyscall]" , "[vvar]" ]
4647
48+ ELF_MAGIC_BYTES = b'\x7f ELF\x02 \x01 '
49+ PROC_TIMEOUT = 30
50+ MAX_NOTE_SIZE = 4096
51+ BYTE_ALIGNMENT = 4
52+
4753Vma = namedtuple ('Vma' , 'offset size start end' )
4854Map = namedtuple ('Map' , 'addr perm offset dev inode pathname flag' )
4955
@@ -67,20 +73,47 @@ def normalize(data, encoding='utf-8'):
6773 return data .encode (encoding )
6874
6975
70- def check_output (* args , ** kwargs ):
71- """ Backported implementation for check_output.
72- """
73- out = ''
76+ def check_output_with_timeout (* args , ** kwargs ):
77+ """Enhanced check_output with timeout support for Python 2/3."""
78+ timeout = kwargs .pop ('timeout' , PROC_TIMEOUT )
79+
80+ # SubprocessError is not available in Python 2.7
81+ SubprocessError = (getattr (subprocess , 'SubprocessError' , OSError ), OSError )
82+
7483 try :
75- p = subprocess .Popen (stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
76- * args , ** kwargs )
77- out , err = p .communicate ()
78- if err or p .returncode != 0 :
79- raise OSError ("{0} ({1})" .format (err , p .returncode ))
80- except OSError as e :
81- logging .debug ('Subprocess `%s %s` error: %s' ,
82- args , kwargs , e )
83- return normalize (out )
84+
85+ def timeout_handler (signum , frame ):
86+ raise OSError ("Command timed out" )
87+
88+ if hasattr (signal , 'SIGALRM' ):
89+ old_handler = signal .signal (signal .SIGALRM , timeout_handler )
90+ signal .alarm (timeout )
91+
92+ try :
93+ p = subprocess .Popen (stdout = subprocess .PIPE , stderr = subprocess .PIPE ,
94+ * args , ** kwargs )
95+ out , err = p .communicate ()
96+
97+ if hasattr (signal , 'SIGALRM' ):
98+ signal .alarm (0 )
99+ signal .signal (signal .SIGALRM , old_handler )
100+
101+ if err or p .returncode != 0 :
102+ raise OSError ("{0} ({1})" .format (normalize (err ), p .returncode ))
103+ return normalize (out )
104+
105+ except OSError :
106+ if hasattr (signal , 'SIGALRM' ):
107+ signal .alarm (0 )
108+ signal .signal (signal .SIGALRM , old_handler )
109+ raise
110+
111+ except SubprocessError as e :
112+ logging .error ('Subprocess error running %s: %s' , args , str (e ))
113+ return ''
114+ except Exception as e :
115+ logging .critical ('Unexpected error running %s: %s' , args , str (e ))
116+ raise
84117
85118
86119def _linux_distribution (* args , ** kwargs ):
@@ -91,11 +124,11 @@ def _linux_distribution(*args, **kwargs):
91124 Additional parameters like `full_distribution_name` are not implemented.
92125 """
93126
94- uname_raw = check_output (['uname' , '-rs' ])
127+ uname_raw = check_output_with_timeout (['uname' , '-rs' ])
95128 uname_name , _ , uname_version = uname_raw .partition (' ' )
96129 uname = {'id' : uname_name .lower (), 'name' : uname_name , 'release' : uname_version }
97130
98- os_release_raw = check_output (['cat' , '/etc/os-release' ])
131+ os_release_raw = check_output_with_timeout (['cat' , '/etc/os-release' ])
99132 os_release = {}
100133 for line in os_release_raw .split ('\n ' ):
101134 k , _ , v = line .partition ('=' )
@@ -109,7 +142,7 @@ def _linux_distribution(*args, **kwargs):
109142 elif k in ('pretty_name' , ):
110143 os_release ['pretty_name_version_id' ] = v .split (' ' )[- 1 ]
111144
112- lsb_release_raw = check_output (['lsb_release' , '-a' ])
145+ lsb_release_raw = check_output_with_timeout (['lsb_release' , '-a' ])
113146 lsb_release = {}
114147 for line in lsb_release_raw .split ('\n ' ):
115148 k , _ , v = line .partition (':' )
@@ -121,11 +154,11 @@ def _linux_distribution(*args, **kwargs):
121154 elif k in ('distributor id' , ):
122155 lsb_release ['distributor_id' ] = v
123156 elif k in ('description' , ):
124- lsb_release ['desciption_version_id ' ] = 'test '
157+ lsb_release ['description_version_id ' ] = v . split ( ' ' )[ - 1 ] if v else ' '
125158
126- for dist_file in sorted (check_output (['ls' , '/etc' ]).split ('\n ' )):
159+ for dist_file in sorted (check_output_with_timeout (['ls' , '/etc' ]).split ('\n ' )):
127160 if (dist_file .endswith ('-release' ) or dist_file .endswith ('_version' )):
128- distro_release_raw = check_output (['cat' , os .path .join ('/etc' , dist_file )])
161+ distro_release_raw = check_output_with_timeout (['cat' , os .path .join ('/etc' , dist_file )])
129162 if distro_release_raw :
130163 break
131164
@@ -193,7 +226,7 @@ def get_patched_data():
193226 return result
194227
195228 try :
196- std_out = check_output ([LIBCARE_CLIENT , 'info' , '-j' ])
229+ std_out = check_output_with_timeout ([LIBCARE_CLIENT , 'info' , '-j' ])
197230 for line in std_out .splitlines ():
198231 try :
199232 item = json .loads (line )
@@ -259,7 +292,7 @@ def get_build_id(fileobj):
259292 e_shentsize , e_shnum , e_shstrndx ) = hdr
260293
261294 # Not an ELF file
262- if not e_ident .startswith (b' \x7f ELF \x02 \x01 ' ):
295+ if not e_ident .startswith (ELF_MAGIC_BYTES ):
263296 raise NotAnELFException ("Wrong header" )
264297
265298 # No program headers
@@ -284,13 +317,15 @@ def get_build_id(fileobj):
284317 n_namesz , n_descsz , n_type = struct .unpack (ELF_NHDR , nhdr )
285318
286319 # 4-byte align
287- if n_namesz % 4 :
288- n_namesz = ((n_namesz // 4 ) + 1 ) * 4
289- if n_descsz % 4 :
290- n_descsz = ((n_descsz // 4 ) + 1 ) * 4
320+ if n_namesz % BYTE_ALIGNMENT :
321+ n_namesz = ((n_namesz // BYTE_ALIGNMENT ) + 1 ) * BYTE_ALIGNMENT
322+ if n_descsz % BYTE_ALIGNMENT :
323+ n_descsz = ((n_descsz // BYTE_ALIGNMENT ) + 1 ) * BYTE_ALIGNMENT
291324
292325 logging .debug ("n_type: %d, n_namesz: %d, n_descsz: %d)" ,
293326 n_type , n_namesz , n_descsz )
327+ if n_namesz > MAX_NOTE_SIZE or n_descsz > MAX_NOTE_SIZE :
328+ raise BuildIDParsingException ("Note section too large" )
294329 fileobj .read (n_namesz )
295330 desc = struct .unpack ("<{0}B" .format (n_descsz ), fileobj .read (n_descsz ))
296331 if n_type is not None :
@@ -418,10 +453,10 @@ def iter_proc_lib():
418453 with get_fileobj (pid , inode , pathname ) as fileobj :
419454 cache [inode ] = get_build_id (fileobj )
420455 except (NotAnELFException , BuildIDParsingException , IOError ) as err :
421- logging .info ("Can't read buildID from {0}: {1}" . format ( pathname , repr (err ) ))
456+ logging .info ("Can't read buildID from %s: %s" , pathname , repr (err ))
422457 cache [inode ] = None
423458 except Exception as err :
424- logging .error ("Can't read buildID from {0}: {1}" . format ( pathname , repr (err ) ))
459+ logging .error ("Can't read buildID from %s: %s" , pathname , repr (err ))
425460 cache [inode ] = None
426461 build_id = cache [inode ]
427462 yield pid , os .path .basename (pathname ), build_id
0 commit comments