Skip to content

Commit 51a1eb1

Browse files
authored
[device/celestica] Mitigation for command injection vulnerability (#11740)
Signed-off-by: maipbui <maibui@microsoft.com> Dependency: [PR (#12065)](#12065) needs to merge first. #### Why I did it 1. `eval()` - not secure against maliciously constructed input, can be dangerous if used to evaluate dynamic content. This may be a code injection vulnerability. 2. `subprocess()` - when using with `shell=True` is dangerous. Using subprocess function without a static string can lead to command injection. 3. `os` - not secure against maliciously constructed input and dangerous if used to evaluate dynamic content. 4. `is` operator - string comparison should not be used with reference equality. 5. `globals()` - extremely dangerous because it may allow an attacker to execute arbitrary code on the system #### How I did it 1. `eval()` - use `literal_eval()` 2. `subprocess()` - use `shell=False` instead. use an array string. Ref: [https://semgrep.dev/docs/cheat-sheets/python-command-injection/#mitigation](https://semgrep.dev/docs/cheat-sheets/python-command-injection/#mitigation) 3. `os` - use with `subprocess` 4. `is` - replace by `==` operator for value equality 5. `globals()` - avoid the use of globals()
1 parent d9eec94 commit 51a1eb1

File tree

22 files changed

+159
-303
lines changed

22 files changed

+159
-303
lines changed

device/celestica/x86_64-cel_e1031-r0/sonic_platform/chassis.py

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
RESET_REGISTER = "0x112"
2525
HOST_REBOOT_CAUSE_PATH = "/host/reboot-cause/previous-reboot-cause.txt"
2626
PMON_REBOOT_CAUSE_PATH = "/usr/share/sonic/platform/api_files/reboot-cause/previous-reboot-cause.txt"
27-
HOST_CHK_CMD = "docker > /dev/null 2>&1"
2827
STATUS_LED_PATH = "/sys/devices/platform/e1031.smc/master_led"
2928

3029

device/celestica/x86_64-cel_e1031-r0/sonic_platform/common.py

+19-106
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import os
2+
import ast
23
import imp
34
import yaml
45
import subprocess
5-
66
from sonic_py_common import device_info
77

88

@@ -24,7 +24,7 @@ class Common:
2424

2525
SET_METHOD_IPMI = 'ipmitool'
2626
NULL_VAL = 'N/A'
27-
HOST_CHK_CMD = "docker > /dev/null 2>&1"
27+
HOST_CHK_CMD = ["docker"]
2828
REF_KEY = '$ref:'
2929

3030
def __init__(self, conf=None):
@@ -46,8 +46,7 @@ def run_command(self, command):
4646
status = False
4747
output = ""
4848
try:
49-
p = subprocess.Popen(
50-
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
49+
p = subprocess.Popen(command, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
5150
raw_data, err = p.communicate()
5251
if p.returncode == 0:
5352
status, output = True, raw_data.strip()
@@ -67,7 +66,7 @@ def _clean_input(self, input, config):
6766
cleaned_input = input_translator.get(input)
6867

6968
elif type(input_translator) is str:
70-
cleaned_input = eval(input_translator.format(input))
69+
cleaned_input = ast.literal_eval(input_translator.format(input))
7170

7271
return cleaned_input
7372

@@ -77,19 +76,12 @@ def _clean_output(self, index, output, config):
7776
if type(output_translator) is dict:
7877
output = output_translator.get(output)
7978
elif type(output_translator) is str:
80-
output = eval(output_translator.format(output))
79+
output = ast.literal_eval(output_translator.format(output))
8180
elif type(output_translator) is list:
82-
output = eval(output_translator[index].format(output))
81+
output = ast.literal_eval(output_translator[index].format(output))
8382

8483
return output
8584

86-
def _ipmi_get(self, index, config):
87-
argument = config.get('argument')
88-
cmd = config['command'].format(
89-
config['argument'][index]) if argument else config['command']
90-
status, output = self.run_command(cmd)
91-
return output if status else None
92-
9385
def _sysfs_read(self, index, config):
9486
sysfs_path = config.get('sysfs_path')
9587
argument = config.get('argument', '')
@@ -132,10 +124,6 @@ def _sysfs_write(self, index, config, input):
132124
return False, output
133125
return True, output
134126

135-
def _ipmi_set(self, index, config, input):
136-
arg = config['argument'][index].format(input)
137-
return self.run_command(config['command'].format(arg))
138-
139127
def _hex_ver_decode(self, hver, num_of_bits, num_of_points):
140128
ver_list = []
141129
c_bit = 0
@@ -159,14 +147,16 @@ def _get_class(self, config):
159147
return class_
160148

161149
def get_reg(self, path, reg_addr):
162-
cmd = "echo {1} > {0}; cat {0}".format(path, reg_addr)
163-
status, output = self.run_command(cmd)
164-
return output if status else None
150+
with open(path, 'w') as file:
151+
file.write(reg_addr + '\n')
152+
with open(path, 'r') as file:
153+
output = file.readline().strip()
154+
return output
165155

166156
def set_reg(self, path, reg_addr, value):
167-
cmd = "echo {0} {1} > {2}".format(reg_addr, value, path)
168-
status, output = self.run_command(cmd)
169-
return output if status else None
157+
with open(path, 'w') as file:
158+
file.write("{0} {1}\n".format(reg_addr, value))
159+
return None
170160

171161
def read_txt_file(self, path):
172162
try:
@@ -195,7 +185,11 @@ def write_txt_file(self, file_path, value):
195185
return True
196186

197187
def is_host(self):
198-
return os.system(self.HOST_CHK_CMD) == 0
188+
try:
189+
subprocess.call(self.HOST_CHK_CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
190+
except FileNotFoundError:
191+
return False
192+
return True
199193

200194
def load_json_file(self, path):
201195
"""
@@ -221,87 +215,6 @@ def get_config_path(self, config_name):
221215
"""
222216
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)
223217

224-
def get_output(self, index, config, default):
225-
"""
226-
Retrieves the output for each function base on config
227-
228-
Args:
229-
index: An integer containing the index of device.
230-
config: A dict object containing the configuration of specified function.
231-
default: A string containing the default output of specified function.
232-
233-
Returns:
234-
A string containing the output of specified function in config
235-
"""
236-
output_source = config.get('output_source')
237-
238-
if output_source == self.OUTPUT_SOURCE_IPMI:
239-
output = self._ipmi_get(index, config)
240-
241-
elif output_source == self.OUTPUT_SOURCE_GIVEN_VALUE:
242-
output = config["value"]
243-
244-
elif output_source == self.OUTPUT_SOURCE_GIVEN_CLASS:
245-
output = self._get_class(config)
246-
247-
elif output_source == self.OUTPUT_SOURCE_GIVEN_LIST:
248-
output = config["value_list"][index]
249-
250-
elif output_source == self.OUTPUT_SOURCE_SYSFS:
251-
output = self._sysfs_read(index, config)
252-
253-
elif output_source == self.OUTPUT_SOURCE_FUNC:
254-
func_conf = self._main_conf[config['function'][index]]
255-
output = self.get_output(index, func_conf, default)
256-
257-
elif output_source == self.OUTPUT_SOURCE_GIVEN_TXT_FILE:
258-
path = config.get('path')
259-
output = self.read_txt_file(path)
260-
261-
elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_FILE:
262-
path = config.get('path')
263-
hex_ver = self.read_txt_file(path)
264-
output = self._hex_ver_decode(
265-
hex_ver, config['num_of_bits'], config['num_of_points'])
266-
267-
elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_ADDR:
268-
path = config.get('path')
269-
addr = config.get('reg_addr')
270-
hex_ver = self.get_reg(path, addr)
271-
output = self._hex_ver_decode(
272-
hex_ver, config['num_of_bits'], config['num_of_points'])
273-
274-
else:
275-
output = default
276-
277-
return self._clean_output(index, output, config) or default
278-
279-
def set_output(self, index, input, config):
280-
"""
281-
Sets the output of specified function on config
282-
283-
Args:
284-
config: A dict object containing the configuration of specified function.
285-
index: An integer containing the index of device.
286-
input: A string containing the input of specified function.
287-
288-
Returns:
289-
bool: True if set function is successfully, False if not
290-
"""
291-
cleaned_input = self._clean_input(input, config)
292-
if not cleaned_input:
293-
return False
294-
295-
set_method = config.get('set_method')
296-
if set_method == self.SET_METHOD_IPMI:
297-
output = self._ipmi_set(index, config, cleaned_input)[0]
298-
elif set_method == self.OUTPUT_SOURCE_SYSFS:
299-
output = self._sysfs_write(index, config, cleaned_input)[0]
300-
else:
301-
output = False
302-
303-
return output
304-
305218
def get_event(self, timeout, config, sfp_list):
306219
"""
307220
Returns a nested dictionary containing all devices which have

device/celestica/x86_64-cel_e1031-r0/sonic_platform/component.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
try:
1111
import os.path
1212
import shutil
13-
import shlex
1413
import subprocess
1514
from sonic_platform_base.component_base import ComponentBase
1615
except ImportError as e:
@@ -39,8 +38,7 @@ def __init__(self, component_index):
3938
def __run_command(self, command):
4039
# Run bash command and print output to stdout
4140
try:
42-
process = subprocess.Popen(
43-
shlex.split(command), universal_newlines=True, stdout=subprocess.PIPE)
41+
process = subprocess.Popen(command, universal_newlines=True, stdout=subprocess.PIPE)
4442
while True:
4543
output = process.stdout.readline()
4644
if output == '' and process.poll() is not None:
@@ -63,24 +61,22 @@ def __get_bios_version(self):
6361

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

7470
def __get_cpld_version(self):
7571
# Retrieves the CPLD firmware version
7672
cpld_version = dict()
7773
with open(SMC_CPLD_PATH, 'r') as fd:
7874
smc_cpld_version = fd.read()
79-
smc_cpld_version = 'None' if smc_cpld_version is 'None' else "{}.{}".format(
75+
smc_cpld_version = 'None' if smc_cpld_version == 'None' else "{}.{}".format(
8076
int(smc_cpld_version[2], 16), int(smc_cpld_version[3], 16))
8177

8278
mmc_cpld_version = self.get_register_value(MMC_CPLD_ADDR)
83-
mmc_cpld_version = 'None' if mmc_cpld_version is 'None' else "{}.{}".format(
79+
mmc_cpld_version = 'None' if mmc_cpld_version == 'None' else "{}.{}".format(
8480
int(mmc_cpld_version[2], 16), int(mmc_cpld_version[3], 16))
8581

8682
cpld_version["SMC_CPLD"] = smc_cpld_version
@@ -159,7 +155,7 @@ def install_firmware(self, image_path):
159155
ext = ".vme" if ext == "" else ext
160156
new_image_path = os.path.join("/tmp", (root.lower() + ext))
161157
shutil.copy(image_path, new_image_path)
162-
install_command = "ispvm %s" % new_image_path
158+
install_command = ["ispvm", str(new_image_path)]
163159
# elif self.name == "BIOS":
164160
# install_command = "afulnx_64 %s /p /b /n /x /r" % image_path
165161
return self.__run_command(install_command)

device/celestica/x86_64-cel_e1031-r0/sonic_platform/thermal_actions.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,5 @@ def execute(self, thermal_info_dict):
7373
thermal_overload_position = Common().read_txt_file(
7474
thermal_overload_position_path)
7575

76-
cmd = 'bash /usr/share/sonic/platform/thermal_overload_control.sh {}'.format(
77-
thermal_overload_position)
76+
cmd = ['bash', '/usr/share/sonic/platform/thermal_overload_control.sh', thermal_overload_position]
7877
Common().run_command(cmd)

device/celestica/x86_64-cel_e1031-r0/sonic_platform/thermal_manager.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
class ThermalManager(ThermalManagerBase):
9-
FSC_ALGORITHM_CMD = ' supervisorctl {} fancontrol'
9+
FSC_ALGORITHM_CMD = ['supervisorctl', '', 'fancontrol']
1010

1111
@classmethod
1212
def start_thermal_control_algorithm(cls):
@@ -43,5 +43,5 @@ def _enable_fancontrol_service(cls, enable):
4343
Returns:
4444
bool: True if set success, False if fail.
4545
"""
46-
cmd = 'start' if enable else 'stop'
47-
return Common().run_command(cls.FSC_ALGORITHM_CMD.format(cmd))
46+
cls.FSC_ALGORITHM_CMD[1] = 'start' if enable else 'stop'
47+
return Common().run_command(cls.FSC_ALGORITHM_CMD)

device/celestica/x86_64-cel_midstone-r0/plugins/psuutil.py

-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#
77
#############################################################################
88

9-
import os.path
10-
import subprocess
119

1210
try:
1311
from sonic_psu.psu_base import PsuBase

device/celestica/x86_64-cel_seastone-r0/sonic_platform/chassis.py

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
REBOOT_CAUSE_FILE = "reboot-cause.txt"
2727
PREV_REBOOT_CAUSE_FILE = "previous-reboot-cause.txt"
2828
GETREG_PATH = "/sys/devices/platform/dx010_cpld/getreg"
29-
HOST_CHK_CMD = "docker > /dev/null 2>&1"
3029
STATUS_LED_PATH = "/sys/devices/platform/leds_dx010/leds/dx010:green:stat/brightness"
3130

3231

device/celestica/x86_64-cel_seastone-r0/sonic_platform/component.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import os.path
1010
import shutil
11-
import subprocess
1211

1312
try:
1413
from sonic_platform_base.component_base import ComponentBase
@@ -52,12 +51,10 @@ def __get_bios_version(self):
5251

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

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

153-
return self.__run_command(install_command)
150+
return self._api_helper.run_command(install_command)
154151

155152

156153
def update_firmware(self, image_path):

0 commit comments

Comments
 (0)