|
| 1 | +# CWE-1335: Incorrect Bitwise Shift of Integer |
| 2 | + |
| 3 | +Ensure to know what bit-wise shift operators do in case you can not avoid them as recommended in *NUM01-J. Do not perform bitwise and arithmetic operations on the same data* [[SEI CERT JAVA 2024]](https://wiki.sei.cmu.edu/confluence/display/java/NUM01-J.+Do+not+perform+bitwise+and+arithmetic+operations+on+the+same+data) and use math instead. |
| 4 | + |
| 5 | +A need to use bit-wise operations in Python can be due to translations or dealings with `C`, `C++` or `Java`, system libraries, raw binary data, or cryptographic algorithms. Existing Python modules hooking into system `C` libraries for cryptographic functions or math all to avoid the need to implement bit-shifting on a Python level. Bit-shifting can have unexpected outcomes. Python's ctypes module allows integration of `C` based system libraries into Python and direct access to C-type variables that can have different behavior than using high-level Python. |
| 6 | + |
| 7 | +For the sake of simplicity, we only want to look at whole numbers and only understand the output of provided code examples illustrating Python's behavior. |
| 8 | + |
| 9 | +## Bit-shifting Positive vs Negative Numbers |
| 10 | + |
| 11 | +Python tries to avoid issues around signed numbers by storing the sign in a separate data field. Bit-shifting a whole negative number will always remain negative in Python. Positive whole numbers will remain positive in Python or zero. Shifting a positive whole number to the right will never go below zero and shifting a negative number to the left will never go above minus one. Shifting to the left behaves differently between positive and negative numbers. |
| 12 | + |
| 13 | +[*example01.py:*](example01.py) |
| 14 | + |
| 15 | +```py |
| 16 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 17 | +# SPDX-License-Identifier: MIT |
| 18 | +"""example code""" |
| 19 | + |
| 20 | +positive_int: int = 1 |
| 21 | +print(f"+{positive_int} = ", end="") |
| 22 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 23 | + |
| 24 | +negative_int: int = -1 |
| 25 | +print(f"{negative_int} = ", end="") |
| 26 | +print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True)) |
| 27 | +``` |
| 28 | + |
| 29 | +**Output of example01.py:** |
| 30 | + |
| 31 | +```bash |
| 32 | ++1 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' |
| 33 | +-1 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' |
| 34 | +``` |
| 35 | + |
| 36 | +Code `example01.py` shows that a `-1` is stored as hexadecimal `\xff` or binary `11111111`s. Storing a decimal `-2` ends with Hex `xFE` or binary `1110`. Storing decimal `+1` ends as binary `0001`. Knowing this allows understanding of some of the side effects. |
| 37 | + |
| 38 | +A right shift `>>=` on a positive number fills zeros from the left and a negative number would fill one's. A continuation of right shifts will reach a point where there is no more change in both cases. |
| 39 | + |
| 40 | +This behaves differently for a left shift `<<=` operation. As a positive number can reach zero it can remain at zero. A negative whole number can never reach zero and is therefore bound to stay at `-1`. |
| 41 | + |
| 42 | +[*example02.py:*](example02.py) |
| 43 | + |
| 44 | +```py |
| 45 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 46 | +# SPDX-License-Identifier: MIT |
| 47 | +"""example code""" |
| 48 | + |
| 49 | +print("\nstay positive") |
| 50 | +positive_int: int = 1 |
| 51 | +print(f"+{positive_int} = ", end="") |
| 52 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 53 | + |
| 54 | +positive_int >>= 1 |
| 55 | +positive_int >>= 1 |
| 56 | +print(f"+{positive_int} = ", end="") |
| 57 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 58 | + |
| 59 | +positive_int <<= 1 |
| 60 | +positive_int <<= 1 |
| 61 | +positive_int <<= 1000000000 |
| 62 | +print(f"+{positive_int} = ", end="") |
| 63 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 64 | + |
| 65 | + |
| 66 | +print("\nstaying negative") |
| 67 | +negative_int: int = -1 |
| 68 | +print(f"{negative_int} = ", end="") |
| 69 | +print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True)) |
| 70 | + |
| 71 | +negative_int >>= 1 |
| 72 | +negative_int >>= 1 |
| 73 | +negative_int >>= 1 |
| 74 | +print(f"{negative_int} = ", end="") |
| 75 | +print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True)) |
| 76 | + |
| 77 | +negative_int <<= 1 |
| 78 | +print(f"{negative_int} = ", end="") |
| 79 | +print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True)) |
| 80 | + |
| 81 | +negative_int <<= 1 |
| 82 | +print(f"{negative_int} = ", end="") |
| 83 | +print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True)) |
| 84 | + |
| 85 | +``` |
| 86 | + |
| 87 | +**Output of example02.py:** |
| 88 | + |
| 89 | +```bash |
| 90 | +stay positive |
| 91 | ++1 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' |
| 92 | ++0 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
| 93 | ++0 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
| 94 | + |
| 95 | +staying negative |
| 96 | +-1 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' |
| 97 | +-1 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' |
| 98 | +-2 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe' |
| 99 | +-4 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc' |
| 100 | +``` |
| 101 | + |
| 102 | +Code `example02.py` illustrates the different behavior of positive and negative whole number's during shifting. A continuation of shifting a positive whole number to the right and back to the left can remain at zero. This is not true for a negative whole number as they can only reach `-1` and never zero. Moving a negative number back and forth can continue to change. |
| 103 | + |
| 104 | +## Overflow Protection |
| 105 | + |
| 106 | +Continuously shifting a Python `int`class variable will change the storage size while keeping the variable type as class int as demonstrated in the following code `example03.py`: |
| 107 | + |
| 108 | +[*example03.py:*](example03.py) |
| 109 | + |
| 110 | +```py |
| 111 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 112 | +# SPDX-License-Identifier: MIT |
| 113 | +"""example code""" |
| 114 | + |
| 115 | +positive_int: int = 1 |
| 116 | +print(f"Before: {type(positive_int)}") |
| 117 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 118 | +print("\nShifting 1000x\n") |
| 119 | +positive_int <<= 1000 |
| 120 | +print(f"After: {type(positive_int)}") |
| 121 | +print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True)) |
| 122 | +``` |
| 123 | + |
| 124 | +**Output of example03.py:** |
| 125 | + |
| 126 | +```bash |
| 127 | +Before: <class 'int'> |
| 128 | +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' |
| 129 | + |
| 130 | +Shifting 1000x |
| 131 | + |
| 132 | +After: <class 'int'> |
| 133 | +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
| 134 | +``` |
| 135 | + |
| 136 | +Code `example03.py` demonstrates how Python is changing required storage in the background while keeping the class of type `int`. While this does prevent overflow in Python it can have some unexpected issues interacting with other languages. A mistake in a loop can cause Python to just continue to eat up memory while finding a natural ending in other languages. |
| 137 | + |
| 138 | +## Non-Compliant Code Example |
| 139 | + |
| 140 | +Method `shift_right` in `noncompliant01.py` can reach zero for positive numbers but loops forever on negative inputs. |
| 141 | + |
| 142 | +*[noncompliant01.py](noncompliant01.py):* |
| 143 | + |
| 144 | +```python |
| 145 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 146 | +# SPDX-License-Identifier: MIT |
| 147 | +"""Non-compliant Code Example.""" |
| 148 | +from time import sleep |
| 149 | + |
| 150 | + |
| 151 | +def shift_right(value: int) -> int: |
| 152 | + while (value != 0): |
| 153 | + print(f"{value}", flush=True) |
| 154 | + value >>= 1 |
| 155 | + sleep(1) |
| 156 | + return value |
| 157 | + |
| 158 | + |
| 159 | +value = shift_right(10) |
| 160 | +print("Returned", value) |
| 161 | +shift_right(-10) |
| 162 | +print("Will never reach here") |
| 163 | +``` |
| 164 | + |
| 165 | +## Compliant Solution |
| 166 | + |
| 167 | +Bit-shifting is an optimization pattern that works better for languages closer to the CPU than Python. Math in Python is better done by arithmetical functions in Python as stated by *CWE-1335: Promote readability and compatibility by using mathematical written code with arithmetic operations instead of bit-wise operations* [[OpenSSF Secure Coding in Python 2025]](https://github.com/ossf/wg-best-practices-os-developers/blob/main/docs/Secure-Coding-Guide-for-Python/CWE-682/CWE-1335/01/README.md). |
| 168 | +Understanding `ctypes` or `C` requires understanding the *CERT C Coding Standard* [[SEI CERT C 2025]](https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard)and setting boundaries manually in Python. |
| 169 | + |
| 170 | +## Automated Detection |
| 171 | + |
| 172 | +Not available |
| 173 | + |
| 174 | +## Related Guidelines |
| 175 | + |
| 176 | +<table> |
| 177 | +<tr> |
| 178 | +<td> |
| 179 | +<a href="https://github.com/ossf/wg-best-practices-os-developers/tree/main/docs/Secure-Coding-Guide-for-Python">[OpenSSF Secure Coding in Python 2025]</a> |
| 180 | +</td> |
| 181 | +<td> |
| 182 | +<a href="https://github.com/ossf/wg-best-practices-os-developers/blob/main/docs/Secure-Coding-Guide-for-Python/CWE-682/CWE-1335/01/README.md">CWE-1335: Promote readability and compatibility by using mathematical written code with arithmetic operations instead of bit-wise operations</a> |
| 183 | +</td> |
| 184 | +</tr> |
| 185 | +<tr> |
| 186 | +<tr> |
| 187 | +<td> |
| 188 | +<a href="http://cwe.mitre.org/">MITRE CWE</a> |
| 189 | +</td> |
| 190 | +<td> |
| 191 | +Pillar: <a href="https://cwe.mitre.org/data/definitions/682.html"> [CWE-682: Incorrect Calculation]</a> |
| 192 | +</td> |
| 193 | +</tr> |
| 194 | +<tr> |
| 195 | +<td> |
| 196 | +<a href="http://cwe.mitre.org/">MITRE CWE</a> |
| 197 | +</td> |
| 198 | +<td> |
| 199 | +Base: <a href="https://cwe.mitre.org/data/definitions/1335.html">[CWE-1335: Incorrect Bitwise Shift of Integer (4.12)]</a> |
| 200 | +</td> |
| 201 | +</tr> |
| 202 | +<tr> |
| 203 | +<td> |
| 204 | +<a href="https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java">[SEI CERT Oracle Coding Standard for Java]</a> |
| 205 | +</td> |
| 206 | +<td> |
| 207 | +<a href="https://wiki.sei.cmu.edu/confluence/display/java/NUM14-J.+Use+shift+operators+correctly">[NUM14-J. Use shift operators correctly]</a> |
| 208 | +</td> |
| 209 | +</tr> |
| 210 | +<tr> |
| 211 | +<td> |
| 212 | +<a href="https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard">[CERT C Coding Standard]</a> |
| 213 | +</td> |
| 214 | +<td> |
| 215 | +<a href="https://wiki.sei.cmu.edu/confluence/display/c/INT34-C.+Do+not+shift+an+expression+by+a+negative+number+of+bits+or+by+greater+than+or+equal+to+the+number+of+bits+that+exist+in+the+operand">[INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of |
| 216 | +bits that exist in the operand]</a> |
| 217 | +</td> |
| 218 | +</tr> |
| 219 | +</table> |
| 220 | + |
| 221 | +## Bibliography |
| 222 | + |
| 223 | +<table> |
| 224 | +<tr> |
| 225 | +<td> |
| 226 | +[SEI CERT JAVA 2024] |
| 227 | +</td> |
| 228 | +<td> |
| 229 | +NUM01-J. Do not perform bitwise and arithmetic operations on the same data [online]. Available from: <a href="https://wiki.sei.cmu.edu/confluence/display/java/NUM01-J.+Do+not+perform+bitwise+and+arithmetic+operations+on+the+same+data">https://wiki.sei.cmu.edu/confluence/display/java/NUM01-J.+Do+not+perform+bitwise+and+arithmetic+operations+on+the+same+data</a>, [Accessed 6 May 2025] |
| 230 | +</td> |
| 231 | +</tr> |
| 232 | +<tr> |
| 233 | +<td> |
| 234 | +[SEI CERT C 2025] |
| 235 | +</td> |
| 236 | +<td> |
| 237 | +CERT C Coding Standard [online]. Available from: <a href=https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard>https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard</a> [Accessed 6 May 2025] |
| 238 | +</td> |
| 239 | +</tr> |
| 240 | +<table> |
0 commit comments