Skip to content

Commit bf65b1e

Browse files
myterons19110
andauthored
pySCG: addign 2nd 1335 rule doc and code (#879)
* pySCG: adding 2nd 1335 rule doc and code as part of #531 * Update docs/Secure-Coding-Guide-for-Python/CWE-682/CWE-1335/README.md * fixing links --------- Signed-off-by: Helge Wehder <[email protected]> Signed-off-by: myteron <[email protected]> Co-authored-by: Hubert Daniszewski <[email protected]>
1 parent 349cadf commit bf65b1e

File tree

6 files changed

+320
-0
lines changed

6 files changed

+320
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
"""example code"""
4+
5+
positive_int: int = 1
6+
print(f"+{positive_int} = ", end="")
7+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
8+
9+
negative_int: int = -1
10+
print(f"{negative_int} = ", end="")
11+
print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True))
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
"""example code"""
4+
5+
print("\nstay positive")
6+
positive_int: int = 1
7+
print(f"+{positive_int} = ", end="")
8+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
9+
10+
positive_int >>= 1
11+
positive_int >>= 1
12+
print(f"+{positive_int} = ", end="")
13+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
14+
15+
positive_int <<= 1
16+
positive_int <<= 1
17+
positive_int <<= 1000000000
18+
print(f"+{positive_int} = ", end="")
19+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
20+
21+
22+
print("\nstaying negative")
23+
negative_int: int = -1
24+
print(f"{negative_int} = ", end="")
25+
print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True))
26+
27+
negative_int >>= 1
28+
negative_int >>= 1
29+
negative_int >>= 1
30+
print(f"{negative_int} = ", end="")
31+
print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True))
32+
33+
negative_int <<= 1
34+
print(f"{negative_int} = ", end="")
35+
print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True))
36+
37+
negative_int <<= 1
38+
print(f"{negative_int} = ", end="")
39+
print(negative_int.to_bytes(negative_int.__sizeof__(), byteorder="big", signed=True))
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
"""example code"""
4+
5+
positive_int: int = 1
6+
print(f"Before: {type(positive_int)}")
7+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
8+
print("\nShifting 1000x\n")
9+
positive_int <<= 1000
10+
print(f"After: {type(positive_int)}")
11+
print(positive_int.to_bytes(positive_int.__sizeof__(), byteorder="big", signed=True))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
"""Non-compliant Code Example."""
4+
from time import sleep
5+
6+
7+
def shift_right(value: int) -> int:
8+
while (value != 0):
9+
print(f"{value}", flush=True)
10+
value >>= 1
11+
sleep(1)
12+
return value
13+
14+
15+
value = shift_right(10)
16+
print("Returned", value)
17+
shift_right(-10)
18+
print("Will never reach here")

docs/Secure-Coding-Guide-for-Python/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ It is __not production code__ and requires code-style or python best practices t
6767
|[CWE-682: Incorrect Calculation](https://cwe.mitre.org/data/definitions/682.html)|Prominent CVE|
6868
|:---------------------------------------------------------------------------------------------------------------|:----|
6969
|[CWE-191: Integer Underflow (Wrap or Wraparound)](CWE-682/CWE-191/README.md)||
70+
|[# CWE-1335: Incorrect Bitwise Shift of Integer](CWE-682/CWE-1335/README.md)||
7071
|[CWE-1335: Promote readability and compatibility by using mathematical written code with arithmetic operations instead of bit-wise operations](CWE-682/CWE-1335/01/README.md)||
7172
|[CWE-1339: Insufficient Precision or Accuracy of a Real Number](CWE-682/CWE-1339/.) ||
7273

0 commit comments

Comments
 (0)