mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-21 12:23:32 +00:00
fix(security): atomic hw-status write, narrow bare excepts, urllib3 CVE floor
- display_manager: replace open()+bare-except with tempfile.mkstemp→fsync→ chmod(0o600)→os.replace; adds symlink guard and logs errors via logger instead of swallowing them silently; pull json/tempfile to module imports - display_manager cleanup(): narrow broad `except Exception: pass` to (OSError, RuntimeError, ValueError, MemoryError) with debug log - api_v3 get_hardware_status(): catch json.JSONDecodeError and PermissionError explicitly; log full traceback server-side; return generic "Unable to read hardware status" to client instead of leaking str(e) - march-madness/requirements.txt: bump urllib3 floor 2.2.2→2.6.3 (CVE fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
requests>=2.33.0
|
requests>=2.33.0
|
||||||
urllib3>=2.2.2
|
urllib3>=2.6.3
|
||||||
Pillow>=12.2.0
|
Pillow>=12.2.0
|
||||||
pytz>=2022.1
|
pytz>=2022.1
|
||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import tempfile
|
||||||
if os.getenv("EMULATOR", "false") == "true":
|
if os.getenv("EMULATOR", "false") == "true":
|
||||||
from RGBMatrixEmulator import RGBMatrix, RGBMatrixOptions
|
from RGBMatrixEmulator import RGBMatrix, RGBMatrixOptions
|
||||||
else:
|
else:
|
||||||
@@ -175,14 +177,28 @@ class DisplayManager:
|
|||||||
# Do not raise here; allow fallback mode so web preview and non-hardware environments work
|
# Do not raise here; allow fallback mode so web preview and non-hardware environments work
|
||||||
|
|
||||||
# Write hardware status file so the web UI can surface init failures
|
# Write hardware status file so the web UI can surface init failures
|
||||||
|
_hw_status = {"ok": self.matrix is not None, "error": _init_error_str}
|
||||||
|
_status_path = "/tmp/led_matrix_hw_status.json" # nosec B108
|
||||||
try:
|
try:
|
||||||
import json as _json
|
if os.path.islink(_status_path):
|
||||||
_hw_status = {"ok": self.matrix is not None, "error": _init_error_str}
|
logger.warning("Skipping hardware status write: %s is a symlink", _status_path)
|
||||||
_status_path = "/tmp/led_matrix_hw_status.json" # nosec B108
|
else:
|
||||||
with open(_status_path, "w") as _f:
|
_fd, _tmp_path = tempfile.mkstemp(dir="/tmp", prefix=".led_hw_") # nosec B108
|
||||||
_json.dump(_hw_status, _f)
|
try:
|
||||||
|
with os.fdopen(_fd, "w") as _f:
|
||||||
|
json.dump(_hw_status, _f)
|
||||||
|
_f.flush()
|
||||||
|
os.fsync(_f.fileno())
|
||||||
|
os.chmod(_tmp_path, 0o600)
|
||||||
|
os.replace(_tmp_path, _status_path)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
os.unlink(_tmp_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
logger.error("Failed to write hardware status file", exc_info=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
@@ -764,8 +780,8 @@ class DisplayManager:
|
|||||||
try:
|
try:
|
||||||
self.image = Image.new('RGB', (self.width, self.height))
|
self.image = Image.new('RGB', (self.width, self.height))
|
||||||
self.draw = ImageDraw.Draw(self.image)
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
except Exception: # nosec B110 - best-effort canvas reset during cleanup; non-critical
|
except (OSError, RuntimeError, ValueError, MemoryError):
|
||||||
pass
|
logger.debug("Canvas reset during cleanup failed", exc_info=True)
|
||||||
# Reset the singleton state when cleaning up
|
# Reset the singleton state when cleaning up
|
||||||
DisplayManager._instance = None
|
DisplayManager._instance = None
|
||||||
DisplayManager._initialized = False
|
DisplayManager._initialized = False
|
||||||
|
|||||||
@@ -1545,8 +1545,12 @@ def get_hardware_status():
|
|||||||
return jsonify({"status": "success", "data": hw_data})
|
return jsonify({"status": "success", "data": hw_data})
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return jsonify({"status": "success", "data": {"ok": None, "error": "Display service not yet started"}})
|
return jsonify({"status": "success", "data": {"ok": None, "error": "Display service not yet started"}})
|
||||||
except Exception as e:
|
except (json.JSONDecodeError, PermissionError):
|
||||||
return jsonify({"status": "error", "message": str(e)}), 500
|
logger.error("Failed to read hardware status file", exc_info=True)
|
||||||
|
return jsonify({"status": "error", "message": "Unable to read hardware status"}), 500
|
||||||
|
except Exception:
|
||||||
|
logger.error("Unexpected error reading hardware status", exc_info=True)
|
||||||
|
return jsonify({"status": "error", "message": "Unable to read hardware status"}), 500
|
||||||
|
|
||||||
@api_v3.route('/display/current', methods=['GET'])
|
@api_v3.route('/display/current', methods=['GET'])
|
||||||
def get_display_current():
|
def get_display_current():
|
||||||
|
|||||||
Reference in New Issue
Block a user