Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[device/celestica] Mitigation for command injection vulnerability #11740

Merged
merged 30 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
956da45
Improve command injection in subprocess and eval
maipbui Aug 15, 2022
8811f7a
Use literal_evals instead of eval
maipbui Aug 15, 2022
652921f
Add sanitize command input
maipbui Aug 15, 2022
c24eec3
Remove globals()
maipbui Aug 15, 2022
9ca1d96
Fix syntax error
maipbui Aug 15, 2022
ca56944
Fix command in subprocess
maipbui Aug 16, 2022
ba61fd4
Change data structure and fix static input in subprocess
maipbui Aug 17, 2022
cebc440
Remove unnecessary parameters
maipbui Aug 18, 2022
0a5d46a
Fix static subprocess
maipbui Aug 18, 2022
dada9ae
Remove os.system and subprocess shell=True
maipbui Sep 1, 2022
91781fe
Fix lgtm
maipbui Sep 1, 2022
6647f81
Fix lgtm
maipbui Sep 1, 2022
35aedce
Remove unused funcs and fix subprocess cmd
maipbui Sep 6, 2022
a7b8055
Remove brackets
maipbui Sep 6, 2022
ec603a0
Add p1 returncod checkere
maipbui Sep 7, 2022
3166477
Replace unsafe functions in platform directory
maipbui Sep 15, 2022
edd4aec
Fix command
maipbui Sep 16, 2022
2d5d44c
Fix command
maipbui Sep 16, 2022
7460c9f
Fix command
maipbui Sep 16, 2022
8ac59ab
Use common lib function
maipbui Sep 18, 2022
f1365f5
Fix PR comments
maipbui Sep 21, 2022
96bc208
Change sp run to call and add \n
maipbui Sep 21, 2022
552abed
Replace shell=True
maipbui Sep 21, 2022
65b4300
fix bug
maipbui Sep 21, 2022
0ce54ef
Add universal_newliness for py3
maipbui Sep 21, 2022
a971633
Merge remote-tracking branch 'upstream/master' into celestica_e1031_s…
maipbui Oct 5, 2022
bba06ff
Revert solution
maipbui Oct 6, 2022
92147d7
Revert deleted line
maipbui Oct 6, 2022
22bad5e
Address PR comments
maipbui Dec 8, 2022
34991a5
Address PR comments
maipbui Dec 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
RESET_REGISTER = "0x112"
HOST_REBOOT_CAUSE_PATH = "/host/reboot-cause/previous-reboot-cause.txt"
PMON_REBOOT_CAUSE_PATH = "/usr/share/sonic/platform/api_files/reboot-cause/previous-reboot-cause.txt"
HOST_CHK_CMD = "docker > /dev/null 2>&1"
STATUS_LED_PATH = "/sys/devices/platform/e1031.smc/master_led"


Expand Down
121 changes: 15 additions & 106 deletions device/celestica/x86_64-cel_e1031-r0/sonic_platform/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os
import ast
import imp
import yaml
import subprocess

from sonic_py_common import device_info


Expand All @@ -24,7 +24,7 @@ class Common:

SET_METHOD_IPMI = 'ipmitool'
NULL_VAL = 'N/A'
HOST_CHK_CMD = "docker > /dev/null 2>&1"
HOST_CHK_CMD = ["docker"]
REF_KEY = '$ref:'

def __init__(self, conf=None):
Expand All @@ -46,8 +46,7 @@ def run_command(self, command):
status = False
output = ""
try:
p = subprocess.Popen(
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(command, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
raw_data, err = p.communicate()
if p.returncode == 0:
status, output = True, raw_data.strip()
Expand All @@ -67,7 +66,7 @@ def _clean_input(self, input, config):
cleaned_input = input_translator.get(input)

elif type(input_translator) is str:
cleaned_input = eval(input_translator.format(input))
cleaned_input = ast.literal_eval(input_translator.format(input))

return cleaned_input

Expand All @@ -77,19 +76,12 @@ def _clean_output(self, index, output, config):
if type(output_translator) is dict:
output = output_translator.get(output)
elif type(output_translator) is str:
output = eval(output_translator.format(output))
output = ast.literal_eval(output_translator.format(output))
elif type(output_translator) is list:
output = eval(output_translator[index].format(output))
output = ast.literal_eval(output_translator[index].format(output))

return output

def _ipmi_get(self, index, config):
argument = config.get('argument')
cmd = config['command'].format(
config['argument'][index]) if argument else config['command']
status, output = self.run_command(cmd)
return output if status else None

def _sysfs_read(self, index, config):
sysfs_path = config.get('sysfs_path')
argument = config.get('argument', '')
Expand Down Expand Up @@ -132,10 +124,6 @@ def _sysfs_write(self, index, config, input):
return False, output
return True, output

def _ipmi_set(self, index, config, input):
arg = config['argument'][index].format(input)
return self.run_command(config['command'].format(arg))

def _hex_ver_decode(self, hver, num_of_bits, num_of_points):
ver_list = []
c_bit = 0
Expand All @@ -159,14 +147,16 @@ def _get_class(self, config):
return class_

def get_reg(self, path, reg_addr):
cmd = "echo {1} > {0}; cat {0}".format(path, reg_addr)
status, output = self.run_command(cmd)
return output if status else None
with open(path, 'w') as file:
file.write(reg_addr + '\n')
with open(path, 'r') as file:
output = file.readline().strip()
return output

def set_reg(self, path, reg_addr, value):
cmd = "echo {0} {1} > {2}".format(reg_addr, value, path)
status, output = self.run_command(cmd)
return output if status else None
with open(path, 'w') as file:
file.write("{0} {1}\n".format(reg_addr, value))
return None

def read_txt_file(self, path):
try:
Expand Down Expand Up @@ -195,7 +185,7 @@ def write_txt_file(self, file_path, value):
return True

def is_host(self):
return os.system(self.HOST_CHK_CMD) == 0
return subprocess.call(self.HOST_CHK_CMD) == 0
Copy link
Contributor

@qnos qnos Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If use subprocess.call, need to suppress stdout/stderr and handle docker command not found exception.
The following example codes work in both host and docker mode.

def is_host():
    try:
        subprocess.call(HOST_CHK_CMD, stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL)
    except FileNotFoundError:
        return False
    return True

Copy link
Contributor Author

@maipbui maipbui Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think os.system is equivalent with subprocess.call without specifying stdout and stderr to DEVNULL. Ref: https://docs.python.org/3/library/subprocess.html#replacing-os-system

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got what you meant, I will change per your suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think os.system is equivalent with subprocess.call without specifying stdout and stderr to DEVNULL. Ref: https://docs.python.org/3/library/subprocess.html#replacing-os-system

Yes, no essential differences between the two functions, but HOST_CHK_CMD had been changed from "docker > /dev/null 2>&1" to ["docker"], so there is no way to suppress the 'docker' command output any more in host mode. And handling the FileNotFoundError exception is also necessary for docker mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Could you review and approve? Thank you!


def load_json_file(self, path):
"""
Expand All @@ -221,87 +211,6 @@ def get_config_path(self, config_name):
"""
return os.path.join(self.DEVICE_PATH, self.platform, self.CONFIG_DIR, config_name) if self.is_host() else os.path.join(self.PMON_PLATFORM_PATH, self.CONFIG_DIR, config_name)

def get_output(self, index, config, default):
"""
Retrieves the output for each function base on config

Args:
index: An integer containing the index of device.
config: A dict object containing the configuration of specified function.
default: A string containing the default output of specified function.

Returns:
A string containing the output of specified function in config
"""
output_source = config.get('output_source')

if output_source == self.OUTPUT_SOURCE_IPMI:
output = self._ipmi_get(index, config)

elif output_source == self.OUTPUT_SOURCE_GIVEN_VALUE:
output = config["value"]

elif output_source == self.OUTPUT_SOURCE_GIVEN_CLASS:
output = self._get_class(config)

elif output_source == self.OUTPUT_SOURCE_GIVEN_LIST:
output = config["value_list"][index]

elif output_source == self.OUTPUT_SOURCE_SYSFS:
output = self._sysfs_read(index, config)

elif output_source == self.OUTPUT_SOURCE_FUNC:
func_conf = self._main_conf[config['function'][index]]
output = self.get_output(index, func_conf, default)

elif output_source == self.OUTPUT_SOURCE_GIVEN_TXT_FILE:
path = config.get('path')
output = self.read_txt_file(path)

elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_FILE:
path = config.get('path')
hex_ver = self.read_txt_file(path)
output = self._hex_ver_decode(
hex_ver, config['num_of_bits'], config['num_of_points'])

elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_ADDR:
path = config.get('path')
addr = config.get('reg_addr')
hex_ver = self.get_reg(path, addr)
output = self._hex_ver_decode(
hex_ver, config['num_of_bits'], config['num_of_points'])

else:
output = default

return self._clean_output(index, output, config) or default

def set_output(self, index, input, config):
"""
Sets the output of specified function on config

Args:
config: A dict object containing the configuration of specified function.
index: An integer containing the index of device.
input: A string containing the input of specified function.

Returns:
bool: True if set function is successfully, False if not
"""
cleaned_input = self._clean_input(input, config)
if not cleaned_input:
return False

set_method = config.get('set_method')
if set_method == self.SET_METHOD_IPMI:
output = self._ipmi_set(index, config, cleaned_input)[0]
elif set_method == self.OUTPUT_SOURCE_SYSFS:
output = self._sysfs_write(index, config, cleaned_input)[0]
else:
output = False

return output

def get_event(self, timeout, config, sfp_list):
"""
Returns a nested dictionary containing all devices which have
Expand Down
20 changes: 8 additions & 12 deletions device/celestica/x86_64-cel_e1031-r0/sonic_platform/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
try:
import os.path
import shutil
import shlex
import subprocess
from sonic_platform_base.component_base import ComponentBase
except ImportError as e:
Expand Down Expand Up @@ -39,8 +38,7 @@ def __init__(self, component_index):
def __run_command(self, command):
# Run bash command and print output to stdout
try:
process = subprocess.Popen(
shlex.split(command), universal_newlines=True, stdout=subprocess.PIPE)
process = subprocess.Popen(command, universal_newlines=True, stdout=subprocess.PIPE)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
Expand All @@ -63,24 +61,22 @@ def __get_bios_version(self):

def get_register_value(self, register):
# Retrieves the cpld register value
cmd = "echo {1} > {0}; cat {0}".format(GETREG_PATH, register)
p = subprocess.Popen(
cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
raw_data, err = p.communicate()
if err is not '':
return None
with open(GETREG_PATH, 'w') as file:
file.write(register + '\n')
with open(GETREG_PATH, 'r') as file:
raw_data = file.readline()
return raw_data.strip()

def __get_cpld_version(self):
# Retrieves the CPLD firmware version
cpld_version = dict()
with open(SMC_CPLD_PATH, 'r') as fd:
smc_cpld_version = fd.read()
smc_cpld_version = 'None' if smc_cpld_version is 'None' else "{}.{}".format(
smc_cpld_version = 'None' if smc_cpld_version == 'None' else "{}.{}".format(
int(smc_cpld_version[2], 16), int(smc_cpld_version[3], 16))

mmc_cpld_version = self.get_register_value(MMC_CPLD_ADDR)
mmc_cpld_version = 'None' if mmc_cpld_version is 'None' else "{}.{}".format(
mmc_cpld_version = 'None' if mmc_cpld_version == 'None' else "{}.{}".format(
int(mmc_cpld_version[2], 16), int(mmc_cpld_version[3], 16))

cpld_version["SMC_CPLD"] = smc_cpld_version
Expand Down Expand Up @@ -159,7 +155,7 @@ def install_firmware(self, image_path):
ext = ".vme" if ext == "" else ext
new_image_path = os.path.join("/tmp", (root.lower() + ext))
shutil.copy(image_path, new_image_path)
install_command = "ispvm %s" % new_image_path
install_command = ["ispvm", str(new_image_path)]
# elif self.name == "BIOS":
# install_command = "afulnx_64 %s /p /b /n /x /r" % image_path
return self.__run_command(install_command)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,5 @@ def execute(self, thermal_info_dict):
thermal_overload_position = Common().read_txt_file(
thermal_overload_position_path)

cmd = 'bash /usr/share/sonic/platform/thermal_overload_control.sh {}'.format(
thermal_overload_position)
cmd = ['bash', '/usr/share/sonic/platform/thermal_overload_control.sh', thermal_overload_position]
Common().run_command(cmd)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class ThermalManager(ThermalManagerBase):
FSC_ALGORITHM_CMD = ' supervisorctl {} fancontrol'
FSC_ALGORITHM_CMD = ['supervisorctl', '', 'fancontrol']

@classmethod
def start_thermal_control_algorithm(cls):
Expand Down Expand Up @@ -43,5 +43,5 @@ def _enable_fancontrol_service(cls, enable):
Returns:
bool: True if set success, False if fail.
"""
cmd = 'start' if enable else 'stop'
return Common().run_command(cls.FSC_ALGORITHM_CMD.format(cmd))
cls.FSC_ALGORITHM_CMD[1] = 'start' if enable else 'stop'
return Common().run_command(cls.FSC_ALGORITHM_CMD)
2 changes: 0 additions & 2 deletions device/celestica/x86_64-cel_midstone-r0/plugins/psuutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#
#############################################################################

import os.path
import subprocess

try:
from sonic_psu.psu_base import PsuBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
REBOOT_CAUSE_FILE = "reboot-cause.txt"
PREV_REBOOT_CAUSE_FILE = "previous-reboot-cause.txt"
GETREG_PATH = "/sys/devices/platform/dx010_cpld/getreg"
HOST_CHK_CMD = "docker > /dev/null 2>&1"
STATUS_LED_PATH = "/sys/devices/platform/leds_dx010/leds/dx010:green:stat/brightness"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import os.path
import shutil
import subprocess

try:
from sonic_platform_base.component_base import ComponentBase
Expand Down Expand Up @@ -52,12 +51,10 @@ def __get_bios_version(self):

def get_register_value(self, register):
# Retrieves the cpld register value
cmd = "echo {1} > {0}; cat {0}".format(GETREG_PATH, register)
p = subprocess.Popen(
cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
raw_data, err = p.communicate()
if err is not '':
return None
with open(GETREG_PATH, 'w') as file:
file.write(register + '\n')
with open(GETREG_PATH, 'r') as file:
raw_data = file.readline()
return raw_data.strip()

def __get_cpld_version(self):
Expand Down Expand Up @@ -146,11 +143,11 @@ def install_firmware(self, image_path):
ext = ".vme" if ext == "" else ext
new_image_path = os.path.join("/tmp", (root.lower() + ext))
shutil.copy(image_path, new_image_path)
install_command = "ispvm %s" % new_image_path
install_command = ["ispvm", str(new_image_path)]
# elif self.name == "BIOS":
# install_command = "afulnx_64 %s /p /b /n /x /r" % image_path

return self.__run_command(install_command)
return self._api_helper.run_command(install_command)


def update_firmware(self, image_path):
Expand Down
Loading