Skip to content

Commit f6a0fa6

Browse files
Harshit-Mashrufacebook-github-bot
authored andcommitted
Kill Process Improvements (#545)
Summary: Pull Request resolved: #545 ### Context Currently the `kill_process` action in TTPForge in cases of failure like `Couldn't find process` and `Couldn't kill process` by default throws an exception which leads to TTP being stopped. While this could be a valid usecase in some situations, allowing user control on whether to throw an error or to print an error and return normally helps make the code versatile. Following is the decision making in the initial commit of Kill Process Action: [link](https://app.diagrams.net/#G1A4S3RgNffbaMbp40RCpqvmprH1QQvVvi#%7B%22pageId%22%3A%22CNsj_rZLfA5pAP3afiLK%22%7D) https://pxl.cl/7Lcn3 This flow diagram shows the updated decision making: [link](https://app.diagrams.net/?title=Kill%20Process%20Updated%20Decision%20making.drawio&client=1#G1TePfzl48hKjTKe-K-efOH8F-ASMror7j#%7B%22pageId%22%3A%22CNsj_rZLfA5pAP3afiLK%22%7D) https://pxl.cl/7Lcmh ### Changes * **Code Improvements**: The `kill_process` step has been updated to receive user inputs on actions to take in case of failures like `error_on_find_process_failure` and `error_on_kill_failure`. This facilitates increased control on the code flow by the user to satisfy different needs. * **Documentation**: Added an example TTP for reference on the use of the new user inputs for the step `kill_process`. * **Testing**: Additional test cases have been added to verify the functionality of the added flags on the `kill_process` step. ### Impact * **Improved Usability**: The changes in this diff make it easier for users to control the output of the step on the basis of their needs. (Ignoring or throwing exceptions in cases of failure) Reviewed By: d0n601 Differential Revision: D78521586 fbshipit-source-id: 4014f0d31ad13fa90c4183d680ca858d40fc56a9
1 parent e6a82a1 commit f6a0fa6

File tree

10 files changed

+309
-27
lines changed

10 files changed

+309
-27
lines changed

docs/foundations/actions/kill_process.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
The `kill_process` action can be used to terminate processes running on a system.
44
Check out the TTP below to see how it works:
55

6-
<!-- TODO: Link to add for example TTP after code is landed -->
6+
[Kill Process Unix](https://github.com/facebookincubator/TTPForge/blob/0deabc567751d90078e5db3c2a84574396b43dc1/example-ttps/actions/kill-process/kill-process-unix.yaml)
7+
[Kill Process Windows](https://github.com/facebookincubator/TTPForge/blob/0deabc567751d90078e5db3c2a84574396b43dc1/example-ttps/actions/kill-process/kill-process-windows.yaml)
78

89
You can experiment with the above TTP by installing the `examples` TTP
910
repository (skip this if `ttpforge list repos` shows that the `examples` repo is
@@ -27,10 +28,16 @@ You can specify the following YAML fields for the `kill_process:` action:
2728
you wish to kill
2829
- `kill_process_name:` (type: `string`) the process name of the process that
2930
you wish to kill
31+
- `error_on_find_process_failure:` (type: `bool`) whether to raise an error if
32+
finding the process name/id fails
33+
- `error_on_kill_failure:` (type: `bool`) whether to raise an error if killing
34+
the process fails
3035

3136
## Additional Notes
3237

3338
If both `kill_process_id` and `kill_process_name` are specified, the action
3439
will only consider process ID as long as it is valid.
3540
If an invalid `kill_process_id` is specified, the action will fall back to
3641
using `kill_process_name` to kill the processes.
42+
Both the flags `error_on_find_process_failure` and `error_on_kill_failure` are
43+
set to `false` by default.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
api_version: 2.0
3+
uuid: fbf458fb-c351-4633-97b6-67332f94b7c1
4+
name: "Kill Process Unix Failure"
5+
description: |
6+
"This is an example TTP that kills a ping process on a Unix system that does not exist"
7+
requirements:
8+
platforms:
9+
- os: linux
10+
- os: darwin
11+
steps:
12+
- name: Process to kill started using python
13+
inline: |
14+
ps aux | grep ping
15+
echo "================="
16+
echo "Starting the detached ping process to be killed"
17+
python3 -c "import subprocess,os; subprocess.Popen(['ping', '-c', '100', '127.0.0.1'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, preexec_fn=os.setpgrp)"
18+
echo "================="
19+
ps aux | grep ping | grep -v grep
20+
echo "================="
21+
- name: Killing the ping process
22+
kill_process_id: ""
23+
kill_process_name: "ping123"
24+
error_on_find_process_failure: false
25+
error_on_kill_process_failure: false
26+
- name: Show processes
27+
inline: |
28+
ps aux | grep ping

example-ttps/actions/kill-process/kill-process-unix.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ steps:
2121
- name: Killing the ping process
2222
kill_process_id: ""
2323
kill_process_name: "ping"
24+
error_on_find_process_failure: true
2425
- name: Show processes
2526
inline: |
2627
ps aux | grep ping
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
api_version: 2.0
3+
uuid: eb38a84b-837b-47d9-9373-3d35a824b003
4+
name: "Kill Process Windows Failure"
5+
description: |
6+
"This is an example TTP that kills a ping process on a Windows system that does not exist"
7+
requirements:
8+
platforms:
9+
- os: windows
10+
steps:
11+
- name: Process to kill started using python
12+
executor: cmd
13+
inline: |
14+
tasklist | findstr PING.EXE
15+
echo "Starting the detached ping process to be killed"
16+
python3 -c "import subprocess,os; subprocess.Popen(['ping', '-n', '10', '127.0.0.1'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=8)"
17+
echo "================="
18+
tasklist | findstr PING.EXE
19+
echo "================="
20+
- name: Killing the ping process
21+
kill_process_id: ""
22+
kill_process_name: "PING123"
23+
error_on_find_process_failure: false
24+
- name: Show processes
25+
executor: cmd
26+
inline: |
27+
tasklist | findstr PING.EXE

pkg/blocks/killprocess.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ import (
3232
// Its intended use is simulating malicious programs stopping
3333
// critical applications/processes
3434
type KillProcessStep struct {
35-
actionDefaults `yaml:",inline"`
36-
ProcessID string `yaml:"kill_process_id,omitempty"`
37-
ProcessName string `yaml:"kill_process_name,omitempty"`
35+
actionDefaults `yaml:",inline"`
36+
ProcessID string `yaml:"kill_process_id,omitempty"`
37+
ProcessName string `yaml:"kill_process_name,omitempty"`
38+
ErrorOnFindProcessFailure bool `yaml:"error_on_find_process_failure,omitempty"`
39+
ErrorOnKillFailure bool `yaml:"error_on_kill_failure,omitempty"`
3840
}
3941

4042
// NewKillProcessStep creates a new KillProcessStep instance and returns a pointer to it.
@@ -100,13 +102,26 @@ func (s *KillProcessStep) extractPIDs() ([]int, error) {
100102

101103
if processID > 0 {
102104
logging.L().Infof("Using Process ID: %v", processID)
105+
106+
err := processutils.VerifyPIDExists(processID)
107+
if err != nil {
108+
logging.L().Errorf("Error while trying to verify PID exists: %+v", err)
109+
if s.ErrorOnFindProcessFailure {
110+
return nil, err
111+
}
112+
return []int{}, nil
113+
}
103114
return []int{processID}, nil
104115
} else if s.ProcessName != "" {
105116
logging.L().Infof("Finding processes with name: %v", s.ProcessName)
117+
106118
processes, err := processutils.GetPIDsByName(s.ProcessName)
107119
if err != nil {
108120
logging.L().Errorf("Error while trying to get PIDs from name: %+v", err)
109-
return nil, err
121+
if s.ErrorOnFindProcessFailure {
122+
return nil, err
123+
}
124+
return []int{}, nil
110125
}
111126

112127
pids := make([]int, len(processes))
@@ -126,23 +141,20 @@ func (s *KillProcessStep) extractPIDs() ([]int, error) {
126141
func (s *KillProcessStep) killProcesses(pids []int) error {
127142
logging.L().Infof("Killing the following processes: %v", pids)
128143

129-
throwError := len(pids) == 1
130-
131-
// Throwing error if only 1 PID and we fail to kill
132144
for _, pid := range pids {
133145
proc, err := os.FindProcess(pid)
134146
if err != nil {
135147
logging.L().Errorf("Error while trying to find process with ID: %v; %+v", pid, err)
136-
if throwError {
148+
if s.ErrorOnFindProcessFailure {
137149
return err
138150
}
139151
continue
140152
}
141153

142-
logging.L().Infof("Got process handle for PID %d: %+v", pid, proc)
154+
logging.L().Infof("Got process handle with PID: %d", pid)
143155
if err := proc.Kill(); err != nil {
144156
logging.L().Errorf("Failed to kill process with ID: %v; %+v", pid, err)
145-
if throwError {
157+
if s.ErrorOnKillFailure {
146158
return err
147159
}
148160
continue
@@ -161,6 +173,10 @@ func (s *KillProcessStep) Execute(_ TTPExecutionContext) (*ActResult, error) {
161173
return nil, err
162174
}
163175

176+
if len(pids) == 0 {
177+
logging.L().Infof("No processes found to kill")
178+
return &ActResult{}, nil
179+
}
164180
if err := s.killProcesses(pids); err != nil {
165181
return nil, err
166182
}

pkg/blocks/killprocess_test.go

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ THE SOFTWARE.
2020
package blocks
2121

2222
import (
23-
"fmt"
2423
"os"
2524
"strconv"
2625
"testing"
@@ -42,33 +41,59 @@ func TestKillProcessExecute(t *testing.T) {
4241
expectValidateError bool
4342
}{
4443
{
45-
name: "Kill non-existent process with process id",
44+
name: "Kill non-existent process with process id - throw error",
4645
description: "Trying to kill a process with process id that doesn't exist",
4746
step: &KillProcessStep{
48-
ProcessID: "123456789",
49-
ProcessName: "ping",
47+
ProcessID: "123",
48+
ProcessName: "ping",
49+
ErrorOnFindProcessFailure: true,
50+
},
51+
createProcess: false,
52+
expectExecuteError: true,
53+
},
54+
{
55+
name: "Kill non-existent process with process id - continue on error",
56+
description: "Trying to kill a process with process id that doesn't exist",
57+
step: &KillProcessStep{
58+
ProcessID: "123456789",
59+
ProcessName: "ping",
60+
ErrorOnFindProcessFailure: false,
61+
ErrorOnKillFailure: false,
62+
},
63+
createProcess: false,
64+
expectExecuteError: false,
65+
},
66+
{
67+
// Test might throw an error on stress run
68+
name: "Kill non-existent process with process name - throw error",
69+
description: "Trying to kill a process with process name that doesn't exist",
70+
step: &KillProcessStep{
71+
ProcessID: "",
72+
ProcessName: "ping",
73+
ErrorOnFindProcessFailure: true,
5074
},
5175
createProcess: false,
5276
expectExecuteError: true,
5377
},
5478
{
55-
name: "Kill non-existent process with process name",
79+
name: "Kill non-existent process with process name - continue on error",
5680
description: "Trying to kill a process with process name that doesn't exist",
5781
step: &KillProcessStep{
5882
ProcessID: "",
5983
ProcessName: "ping",
6084
},
6185
createProcess: false,
62-
expectExecuteError: true,
86+
expectExecuteError: false,
6387
},
6488
{
65-
name: "Kill existent process with process id",
89+
name: "Kill non-existent process with process id",
6690
description: "Trying to kill a process with process id that exists",
6791
step: &KillProcessStep{
6892
ProcessID: "123456789",
6993
ProcessName: "ping",
7094
},
71-
createProcess: true,
95+
createProcess: true,
96+
expectExecuteError: false,
7297
},
7398
{
7499
name: "Kill existent process with process name",
@@ -77,7 +102,8 @@ func TestKillProcessExecute(t *testing.T) {
77102
ProcessID: "",
78103
ProcessName: "ping",
79104
},
80-
createProcess: true,
105+
createProcess: true,
106+
expectExecuteError: false,
81107
},
82108
{
83109
name: "Kill process invalid process id",
@@ -110,8 +136,9 @@ func TestKillProcessExecute(t *testing.T) {
110136
name: "Kill process ID with templating",
111137
description: "Trying to kill a process with templating",
112138
step: &KillProcessStep{
113-
ProcessID: "{[{.StepVars.pid}]}",
114-
ProcessName: "ping",
139+
ProcessID: "{[{.StepVars.pid}]}",
140+
ProcessName: "ping",
141+
ErrorOnFindProcessFailure: true,
115142
},
116143
stepVars: map[string]string{
117144
"pid": "123456789",
@@ -124,8 +151,9 @@ func TestKillProcessExecute(t *testing.T) {
124151
name: "Kill process name with templating",
125152
description: "Trying to kill a process with process name and templating",
126153
step: &KillProcessStep{
127-
ProcessID: "",
128-
ProcessName: "{[{.StepVars.processName}]}",
154+
ProcessID: "",
155+
ProcessName: "{[{.StepVars.processName}]}",
156+
ErrorOnFindProcessFailure: true,
129157
},
130158
stepVars: map[string]string{
131159
"processName": "touch",
@@ -175,7 +203,6 @@ func TestKillProcessExecute(t *testing.T) {
175203
require.Error(t, err2)
176204
return
177205
}
178-
fmt.Printf(tc.step.ProcessID)
179206
require.NoError(t, err2)
180207

181208
if tc.createProcess {

pkg/processutils/processutils.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ func GetPIDsByName(processName string) ([]int32, error) {
4242
}
4343
return pids, nil
4444
}
45+
46+
// VerifyPIDExists returns a boolean basis if a process with the input PID exists
47+
func VerifyPIDExists(pid int) error {
48+
processes, err := process.Processes()
49+
if err != nil {
50+
return err
51+
}
52+
for _, proc := range processes {
53+
if int(proc.Pid) == pid {
54+
return nil
55+
}
56+
}
57+
return fmt.Errorf("No process found with PID: %d", pid)
58+
}

pkg/processutils/processutils_test.go renamed to pkg/processutils/processutils_unix_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build !windows
2+
// +build !windows
3+
14
/*
25
Copyright © 2023-present, Meta Platforms, Inc. and affiliates
36
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -44,7 +47,7 @@ func TestGetPidsByName(t *testing.T) {
4447
},
4548
{
4649
name: "Process Does Not Exist",
47-
processName: "ping",
50+
processName: "pingabc",
4851
processExists: false,
4952
expectEntriesInResult: false,
5053
},
@@ -89,3 +92,38 @@ func TestGetPidsByName(t *testing.T) {
8992
})
9093
}
9194
}
95+
96+
// Only PID=1 is always running in Unix & Linux but not windows
97+
func TestVerifyPIDExists(t *testing.T) {
98+
99+
testCases := []struct {
100+
name string
101+
pid int
102+
expectError bool
103+
}{
104+
{
105+
name: "Process Exists",
106+
pid: 1,
107+
expectError: false,
108+
},
109+
{
110+
name: "Process Does Not Exist",
111+
pid: 124567890987654321,
112+
expectError: true,
113+
},
114+
}
115+
116+
for _, tc := range testCases {
117+
t.Run(tc.name, func(t *testing.T) {
118+
119+
// Testing
120+
err := VerifyPIDExists(tc.pid)
121+
122+
if tc.expectError {
123+
require.Error(t, err)
124+
} else {
125+
require.NoError(t, err)
126+
}
127+
})
128+
}
129+
}

0 commit comments

Comments
 (0)