xref: /aosp_15_r20/external/google-breakpad/src/processor/disassembler_x86.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2010 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 // disassembler_x86.cc: simple x86 disassembler.
30 //
31 // Provides single step disassembly of x86 bytecode and flags instructions
32 // that utilize known bad register values.
33 //
34 // Author: Cris Neckar
35 
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>  // Must come first
38 #endif
39 
40 #include "processor/disassembler_x86.h"
41 
42 #include <string.h>
43 
44 namespace google_breakpad {
45 
DisassemblerX86(const uint8_t * bytecode,uint32_t size,uint32_t virtual_address)46 DisassemblerX86::DisassemblerX86(const uint8_t* bytecode,
47                                  uint32_t size,
48                                  uint32_t virtual_address) :
49                                      bytecode_(bytecode),
50                                      size_(size),
51                                      virtual_address_(virtual_address),
52                                      current_byte_offset_(0),
53                                      current_inst_offset_(0),
54                                      instr_valid_(false),
55                                      register_valid_(false),
56                                      pushed_bad_value_(false),
57                                      end_of_block_(false),
58                                      flags_(0) {
59   libdis::x86_init(libdis::opt_none, NULL, NULL);
60 }
61 
~DisassemblerX86()62 DisassemblerX86::~DisassemblerX86() {
63   if (instr_valid_)
64     libdis::x86_oplist_free(&current_instr_);
65 
66   libdis::x86_cleanup();
67 }
68 
NextInstruction()69 uint32_t DisassemblerX86::NextInstruction() {
70   if (instr_valid_)
71     libdis::x86_oplist_free(&current_instr_);
72 
73   if (current_byte_offset_ >= size_) {
74     instr_valid_ = false;
75     return 0;
76   }
77   uint32_t instr_size = 0;
78   instr_size = libdis::x86_disasm((unsigned char*)bytecode_, size_,
79                           virtual_address_, current_byte_offset_,
80                           &current_instr_);
81   if (instr_size == 0) {
82     instr_valid_ = false;
83     return 0;
84   }
85 
86   current_byte_offset_ += instr_size;
87   current_inst_offset_++;
88   instr_valid_ = libdis::x86_insn_is_valid(&current_instr_);
89   if (!instr_valid_)
90     return 0;
91 
92   if (current_instr_.type == libdis::insn_return)
93     end_of_block_ = true;
94   libdis::x86_op_t* src = libdis::x86_get_src_operand(&current_instr_);
95   libdis::x86_op_t* dest = libdis::x86_get_dest_operand(&current_instr_);
96 
97   if (register_valid_) {
98     switch (current_instr_.group) {
99       // Flag branches based off of bad registers and calls that occur
100       // after pushing bad values.
101       case libdis::insn_controlflow:
102         switch (current_instr_.type) {
103           case libdis::insn_jmp:
104           case libdis::insn_jcc:
105           case libdis::insn_call:
106           case libdis::insn_callcc:
107             if (dest) {
108               switch (dest->type) {
109                 case libdis::op_expression:
110                   if (dest->data.expression.base.id == bad_register_.id)
111                     flags_ |= DISX86_BAD_BRANCH_TARGET;
112                   break;
113                 case libdis::op_register:
114                   if (dest->data.reg.id == bad_register_.id)
115                     flags_ |= DISX86_BAD_BRANCH_TARGET;
116                   break;
117                 default:
118                   if (pushed_bad_value_ &&
119                       (current_instr_.type == libdis::insn_call ||
120                       current_instr_.type == libdis::insn_callcc))
121                     flags_ |= DISX86_BAD_ARGUMENT_PASSED;
122                   break;
123               }
124             }
125             break;
126           default:
127             break;
128         }
129         break;
130 
131       // Flag block data operations that use bad registers for src or dest.
132       case libdis::insn_string:
133         if (dest && dest->type == libdis::op_expression &&
134             dest->data.expression.base.id == bad_register_.id)
135           flags_ |= DISX86_BAD_BLOCK_WRITE;
136         if (src && src->type == libdis::op_expression &&
137             src->data.expression.base.id == bad_register_.id)
138           flags_ |= DISX86_BAD_BLOCK_READ;
139         break;
140 
141       // Flag comparisons based on bad data.
142       case libdis::insn_comparison:
143         if ((dest && dest->type == libdis::op_expression &&
144             dest->data.expression.base.id == bad_register_.id) ||
145             (src && src->type == libdis::op_expression &&
146             src->data.expression.base.id == bad_register_.id) ||
147             (dest && dest->type == libdis::op_register &&
148             dest->data.reg.id == bad_register_.id) ||
149             (src && src->type == libdis::op_register &&
150             src->data.reg.id == bad_register_.id))
151           flags_ |= DISX86_BAD_COMPARISON;
152         break;
153 
154       // Flag any other instruction which derefs a bad register for
155       // src or dest.
156       default:
157         if (dest && dest->type == libdis::op_expression &&
158             dest->data.expression.base.id == bad_register_.id)
159           flags_ |= DISX86_BAD_WRITE;
160         if (src && src->type == libdis::op_expression &&
161             src->data.expression.base.id == bad_register_.id)
162           flags_ |= DISX86_BAD_READ;
163         break;
164     }
165   }
166 
167   // When a register is marked as tainted check if it is pushed.
168   // TODO(cdn): may also want to check for MOVs into EBP offsets.
169   if (register_valid_ && dest && current_instr_.type == libdis::insn_push) {
170     switch (dest->type) {
171       case libdis::op_expression:
172         if (dest->data.expression.base.id == bad_register_.id ||
173             dest->data.expression.index.id == bad_register_.id)
174           pushed_bad_value_ = true;
175         break;
176       case libdis::op_register:
177         if (dest->data.reg.id == bad_register_.id)
178           pushed_bad_value_ = true;
179         break;
180       default:
181         break;
182     }
183   }
184 
185   // Check if a tainted register value is clobbered.
186   // For conditional MOVs and XCHGs assume that
187   // there is a hit.
188   if (register_valid_) {
189     switch (current_instr_.type) {
190       case libdis::insn_xor:
191         if (src && src->type == libdis::op_register &&
192             dest && dest->type == libdis::op_register &&
193             src->data.reg.id == bad_register_.id &&
194             src->data.reg.id == dest->data.reg.id)
195           register_valid_ = false;
196         break;
197       case libdis::insn_pop:
198       case libdis::insn_mov:
199       case libdis::insn_movcc:
200         if (dest && dest->type == libdis::op_register &&
201             dest->data.reg.id == bad_register_.id)
202           register_valid_ = false;
203         break;
204       case libdis::insn_popregs:
205         register_valid_ = false;
206         break;
207       case libdis::insn_xchg:
208       case libdis::insn_xchgcc:
209         if (dest && dest->type == libdis::op_register &&
210             src && src->type == libdis::op_register) {
211           if (dest->data.reg.id == bad_register_.id)
212             memcpy(&bad_register_, &src->data.reg, sizeof(libdis::x86_reg_t));
213           else if (src->data.reg.id == bad_register_.id)
214             memcpy(&bad_register_, &dest->data.reg, sizeof(libdis::x86_reg_t));
215         }
216         break;
217       default:
218         break;
219     }
220   }
221 
222   return instr_size;
223 }
224 
setBadRead()225 bool DisassemblerX86::setBadRead() {
226   if (!instr_valid_)
227     return false;
228 
229   libdis::x86_op_t* operand = libdis::x86_get_src_operand(&current_instr_);
230   if (!operand || operand->type != libdis::op_expression)
231     return false;
232 
233   memcpy(&bad_register_, &operand->data.expression.base,
234          sizeof(libdis::x86_reg_t));
235   register_valid_ = true;
236   return true;
237 }
238 
setBadWrite()239 bool DisassemblerX86::setBadWrite() {
240   if (!instr_valid_)
241     return false;
242 
243   libdis::x86_op_t* operand = libdis::x86_get_dest_operand(&current_instr_);
244   if (!operand || operand->type != libdis::op_expression)
245     return false;
246 
247   memcpy(&bad_register_, &operand->data.expression.base,
248          sizeof(libdis::x86_reg_t));
249   register_valid_ = true;
250   return true;
251 }
252 
253 }  // namespace google_breakpad
254