Skip to content

Commit 9ae674e

Browse files
committed
pySCG: addign 2nd 1335 rule doc and code
Signed-off-by: Helge Wehder <[email protected]>
1 parent f06d79a commit 9ae674e

File tree

6 files changed

+311
-0
lines changed

6 files changed

+311
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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-P3 Do Not Perform Bit-wise and Arithmetic Operations* [[SEI CERT JAVA 2024]](https://eteamspace.internal.ericsson.com/pages/viewpage.action?pageId=1122687324) 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 shiftRight 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 *NUM01-P3 Do Not Perform Bit-wise and Arithmetic Operations on the Same Data* [[SEI CERT JAVA 2024]](https://eteamspace.internal.ericsson.com/pages/viewpage.action?pageId=1122687324).
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="http://cwe.mitre.org/">MITRE CWE</a>
180+
</td>
181+
<td>
182+
Pillar: <a href="https://cwe.mitre.org/data/definitions/682.html"> [CWE-682: Incorrect Calculation]</a>
183+
</td>
184+
</tr>
185+
<tr>
186+
<td>
187+
<a href="http://cwe.mitre.org/">MITRE CWE</a>
188+
</td>
189+
<td>
190+
Base: <a href="https://cwe.mitre.org/data/definitions/1335.html">[CWE-1335: Incorrect Bitwise Shift of Integer (4.12)]</a>
191+
</td>
192+
</tr>
193+
<tr>
194+
<td>
195+
<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>
196+
</td>
197+
<td>
198+
<a href="https://wiki.sei.cmu.edu/confluence/display/java/NUM14-J.+Use+shift+operators+correctly">[NUM14-J. Use shift operators correctly]</a>
199+
</td>
200+
</tr>
201+
<tr>
202+
<td>
203+
<a href="https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Coding+Standard">[CERT C Coding Standard]</a>
204+
</td>
205+
<td>
206+
<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
207+
bits that exist in the operand]</a>
208+
</td>
209+
</tr>
210+
</table>
211+
212+
## Bibliography
213+
214+
<table>
215+
<tr>
216+
<td>
217+
[SEI CERT JAVA 2024]
218+
</td>
219+
<td>
220+
NUM01-P3 Do Not Perform Bit-wise and Arithmetic Operations on the Same Data [online]. Available from: <a href="https://eteamspace.internal.ericsson.com/pages/viewpage.action?pageId=1122687324">https://eteamspace.internal.ericsson.com/pages/viewpage.action?pageId=1122687324</a>, [Accessed 6 May 2025]
221+
</td>
222+
</tr>
223+
<tr>
224+
<td>
225+
[SEI CERT C 2025]
226+
</td>
227+
<td>
228+
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]
229+
</td>
230+
</tr>
231+
<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
@@ -65,6 +65,7 @@ It is __not production code__ and requires code-style or python best practices t
6565
|[CWE-682: Incorrect Calculation](https://cwe.mitre.org/data/definitions/682.html)|Prominent CVE|
6666
|:---------------------------------------------------------------------------------------------------------------|:----|
6767
|[CWE-191: Integer Underflow (Wrap or Wraparound)](CWE-682/CWE-191/README.md)||
68+
|[# CWE-1335: Incorrect Bitwise Shift of Integer](CWE-682/CWE-1335/README.md)||
6869
|[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)||
6970
|[CWE-1339: Insufficient Precision or Accuracy of a Real Number](CWE-682/CWE-1339/.) ||
7071

0 commit comments

Comments
 (0)