summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2024-10-08 13:36:25 +0100
committerSteve Sakoman <steve@sakoman.com>2024-11-30 05:41:59 -0800
commit18612ba9d4287fbafa3a1127e839479b19d6c6fd (patch)
treeef2305554ad6c633bfa7b317739187267c4d819e
parent4d44b6b9ffd58e630febb86503e6b6e5886a94ca (diff)
downloadpoky-18612ba9d4287fbafa3a1127e839479b19d6c6fd.tar.gz
bitbake: Remove custom exception backtrace formatting
Removes the code in bitbake to show custom backtrace formatting for exceptions. In particular, the bitbake exception code prints function arguments, which while helpful is a security problem when passwords and other secrets can be passed as function arguments. As it turns out, the handling of the custom serialized exception stack frames was pretty much made obsolete by d7db75020ed ("event/msg: Pass formatted exceptions"), which changed the events to pass a preformatted stacktrack list of strings, but the passing of the serialized data was never removed. Change all the code to use the python traceback API to format exceptions instead of the custom code; conveniently traceback.format_exception() also returns a list of stack trace strings, so it can be used as a drop in replacement for bb.exception.format_exception() (Bitbake rev: 1f7889c2f5e192fb108b51883aac8685ccb187f2) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--bitbake/lib/bb/cooker.py32
-rw-r--r--bitbake/lib/bb/event.py9
-rw-r--r--bitbake/lib/bb/exceptions.py96
-rw-r--r--bitbake/lib/bb/msg.py4
-rw-r--r--bitbake/lib/bb/ui/teamcity.py5
5 files changed, 25 insertions, 121 deletions
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 2e80986640..582fc35f24 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -17,7 +17,7 @@ import threading
17from io import StringIO, UnsupportedOperation 17from io import StringIO, UnsupportedOperation
18from contextlib import closing 18from contextlib import closing
19from collections import defaultdict, namedtuple 19from collections import defaultdict, namedtuple
20import bb, bb.exceptions, bb.command 20import bb, bb.command
21from bb import utils, data, parse, event, cache, providers, taskdata, runqueue, build 21from bb import utils, data, parse, event, cache, providers, taskdata, runqueue, build
22import queue 22import queue
23import signal 23import signal
@@ -2101,7 +2101,6 @@ class Parser(multiprocessing.Process):
2101 except Exception as exc: 2101 except Exception as exc:
2102 tb = sys.exc_info()[2] 2102 tb = sys.exc_info()[2]
2103 exc.recipe = filename 2103 exc.recipe = filename
2104 exc.traceback = list(bb.exceptions.extract_traceback(tb, context=3))
2105 return True, None, exc 2104 return True, None, exc
2106 # Need to turn BaseExceptions into Exceptions here so we gracefully shutdown 2105 # Need to turn BaseExceptions into Exceptions here so we gracefully shutdown
2107 # and for example a worker thread doesn't just exit on its own in response to 2106 # and for example a worker thread doesn't just exit on its own in response to
@@ -2302,8 +2301,12 @@ class CookerParser(object):
2302 return False 2301 return False
2303 except ParsingFailure as exc: 2302 except ParsingFailure as exc:
2304 self.error += 1 2303 self.error += 1
2305 logger.error('Unable to parse %s: %s' % 2304
2306 (exc.recipe, bb.exceptions.to_string(exc.realexception))) 2305 exc_desc = str(exc)
2306 if isinstance(exc, SystemExit) and not isinstance(exc.code, str):
2307 exc_desc = 'Exited with "%d"' % exc.code
2308
2309 logger.error('Unable to parse %s: %s' % (exc.recipe, exc_desc))
2307 self.shutdown(clean=False) 2310 self.shutdown(clean=False)
2308 return False 2311 return False
2309 except bb.parse.ParseError as exc: 2312 except bb.parse.ParseError as exc:
@@ -2312,20 +2315,33 @@ class CookerParser(object):
2312 self.shutdown(clean=False, eventmsg=str(exc)) 2315 self.shutdown(clean=False, eventmsg=str(exc))
2313 return False 2316 return False
2314 except bb.data_smart.ExpansionError as exc: 2317 except bb.data_smart.ExpansionError as exc:
2318 def skip_frames(f, fn_prefix):
2319 while f and f.tb_frame.f_code.co_filename.startswith(fn_prefix):
2320 f = f.tb_next
2321 return f
2322
2315 self.error += 1 2323 self.error += 1
2316 bbdir = os.path.dirname(__file__) + os.sep 2324 bbdir = os.path.dirname(__file__) + os.sep
2317 etype, value, _ = sys.exc_info() 2325 etype, value, tb = sys.exc_info()
2318 tb = list(itertools.dropwhile(lambda e: e.filename.startswith(bbdir), exc.traceback)) 2326
2327 # Remove any frames where the code comes from bitbake. This
2328 # prevents deep (and pretty useless) backtraces for expansion error
2329 tb = skip_frames(tb, bbdir)
2330 cur = tb
2331 while cur:
2332 cur.tb_next = skip_frames(cur.tb_next, bbdir)
2333 cur = cur.tb_next
2334
2319 logger.error('ExpansionError during parsing %s', value.recipe, 2335 logger.error('ExpansionError during parsing %s', value.recipe,
2320 exc_info=(etype, value, tb)) 2336 exc_info=(etype, value, tb))
2321 self.shutdown(clean=False) 2337 self.shutdown(clean=False)
2322 return False 2338 return False
2323 except Exception as exc: 2339 except Exception as exc:
2324 self.error += 1 2340 self.error += 1
2325 etype, value, tb = sys.exc_info() 2341 _, value, _ = sys.exc_info()
2326 if hasattr(value, "recipe"): 2342 if hasattr(value, "recipe"):
2327 logger.error('Unable to parse %s' % value.recipe, 2343 logger.error('Unable to parse %s' % value.recipe,
2328 exc_info=(etype, value, exc.traceback)) 2344 exc_info=sys.exc_info())
2329 else: 2345 else:
2330 # Most likely, an exception occurred during raising an exception 2346 # Most likely, an exception occurred during raising an exception
2331 import traceback 2347 import traceback
diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py
index 4761c86880..952c85c0bd 100644
--- a/bitbake/lib/bb/event.py
+++ b/bitbake/lib/bb/event.py
@@ -19,7 +19,6 @@ import sys
19import threading 19import threading
20import traceback 20import traceback
21 21
22import bb.exceptions
23import bb.utils 22import bb.utils
24 23
25# This is the pid for which we should generate the event. This is set when 24# This is the pid for which we should generate the event. This is set when
@@ -759,13 +758,7 @@ class LogHandler(logging.Handler):
759 758
760 def emit(self, record): 759 def emit(self, record):
761 if record.exc_info: 760 if record.exc_info:
762 etype, value, tb = record.exc_info 761 record.bb_exc_formatted = traceback.format_exception(*record.exc_info)
763 if hasattr(tb, 'tb_next'):
764 tb = list(bb.exceptions.extract_traceback(tb, context=3))
765 # Need to turn the value into something the logging system can pickle
766 record.bb_exc_info = (etype, value, tb)
767 record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
768 value = str(value)
769 record.exc_info = None 762 record.exc_info = None
770 fire(record, None) 763 fire(record, None)
771 764
diff --git a/bitbake/lib/bb/exceptions.py b/bitbake/lib/bb/exceptions.py
deleted file mode 100644
index 801db9c82f..0000000000
--- a/bitbake/lib/bb/exceptions.py
+++ /dev/null
@@ -1,96 +0,0 @@
1#
2# Copyright BitBake Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import inspect
8import traceback
9import bb.namedtuple_with_abc
10from collections import namedtuple
11
12
13class TracebackEntry(namedtuple.abc):
14 """Pickleable representation of a traceback entry"""
15 _fields = 'filename lineno function args code_context index'
16 _header = ' File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'
17
18 def format(self, formatter=None):
19 if not self.code_context:
20 return self._header.format(self) + '\n'
21
22 formatted = [self._header.format(self) + ':\n']
23
24 for lineindex, line in enumerate(self.code_context):
25 if formatter:
26 line = formatter(line)
27
28 if lineindex == self.index:
29 formatted.append(' >%s' % line)
30 else:
31 formatted.append(' %s' % line)
32 return formatted
33
34 def __str__(self):
35 return ''.join(self.format())
36
37def _get_frame_args(frame):
38 """Get the formatted arguments and class (if available) for a frame"""
39 arginfo = inspect.getargvalues(frame)
40
41 try:
42 if not arginfo.args:
43 return '', None
44 # There have been reports from the field of python 2.6 which doesn't
45 # return a namedtuple here but simply a tuple so fallback gracefully if
46 # args isn't present.
47 except AttributeError:
48 return '', None
49
50 firstarg = arginfo.args[0]
51 if firstarg == 'self':
52 self = arginfo.locals['self']
53 cls = self.__class__.__name__
54
55 arginfo.args.pop(0)
56 del arginfo.locals['self']
57 else:
58 cls = None
59
60 formatted = inspect.formatargvalues(*arginfo)
61 return formatted, cls
62
63def extract_traceback(tb, context=1):
64 frames = inspect.getinnerframes(tb, context)
65 for frame, filename, lineno, function, code_context, index in frames:
66 formatted_args, cls = _get_frame_args(frame)
67 if cls:
68 function = '%s.%s' % (cls, function)
69 yield TracebackEntry(filename, lineno, function, formatted_args,
70 code_context, index)
71
72def format_extracted(extracted, formatter=None, limit=None):
73 if limit:
74 extracted = extracted[-limit:]
75
76 formatted = []
77 for tracebackinfo in extracted:
78 formatted.extend(tracebackinfo.format(formatter))
79 return formatted
80
81
82def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
83 formatted = ['Traceback (most recent call last):\n']
84
85 if hasattr(tb, 'tb_next'):
86 tb = extract_traceback(tb, context)
87
88 formatted.extend(format_extracted(tb, formatter, limit))
89 formatted.extend(traceback.format_exception_only(etype, value))
90 return formatted
91
92def to_string(exc):
93 if isinstance(exc, SystemExit):
94 if not isinstance(exc.code, str):
95 return 'Exited with "%d"' % exc.code
96 return str(exc)
diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
index 3e18596faa..4f616ff42e 100644
--- a/bitbake/lib/bb/msg.py
+++ b/bitbake/lib/bb/msg.py
@@ -89,10 +89,6 @@ class BBLogFormatter(logging.Formatter):
89 msg = logging.Formatter.format(self, record) 89 msg = logging.Formatter.format(self, record)
90 if hasattr(record, 'bb_exc_formatted'): 90 if hasattr(record, 'bb_exc_formatted'):
91 msg += '\n' + ''.join(record.bb_exc_formatted) 91 msg += '\n' + ''.join(record.bb_exc_formatted)
92 elif hasattr(record, 'bb_exc_info'):
93 etype, value, tb = record.bb_exc_info
94 formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
95 msg += '\n' + ''.join(formatted)
96 return msg 92 return msg
97 93
98 def colorize(self, record): 94 def colorize(self, record):
diff --git a/bitbake/lib/bb/ui/teamcity.py b/bitbake/lib/bb/ui/teamcity.py
index fca46c2874..7eeaab8d63 100644
--- a/bitbake/lib/bb/ui/teamcity.py
+++ b/bitbake/lib/bb/ui/teamcity.py
@@ -30,7 +30,6 @@ import bb.build
30import bb.command 30import bb.command
31import bb.cooker 31import bb.cooker
32import bb.event 32import bb.event
33import bb.exceptions
34import bb.runqueue 33import bb.runqueue
35from bb.ui import uihelper 34from bb.ui import uihelper
36 35
@@ -102,10 +101,6 @@ class TeamcityLogFormatter(logging.Formatter):
102 details = "" 101 details = ""
103 if hasattr(record, 'bb_exc_formatted'): 102 if hasattr(record, 'bb_exc_formatted'):
104 details = ''.join(record.bb_exc_formatted) 103 details = ''.join(record.bb_exc_formatted)
105 elif hasattr(record, 'bb_exc_info'):
106 etype, value, tb = record.bb_exc_info
107 formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
108 details = ''.join(formatted)
109 104
110 if record.levelno in [bb.msg.BBLogFormatter.ERROR, bb.msg.BBLogFormatter.CRITICAL]: 105 if record.levelno in [bb.msg.BBLogFormatter.ERROR, bb.msg.BBLogFormatter.CRITICAL]:
111 # ERROR gets a separate errorDetails field 106 # ERROR gets a separate errorDetails field