Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions checksec/binary.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Union

Expand All @@ -11,20 +12,27 @@
from .pe import PEChecksecData


class NX(Enum):
No = 1
Yes = 2
NA = 3


class BinarySecurity(ABC):
def __init__(self, bin_path: Path):
self.bin = lief.parse(str(bin_path))
if not self.bin:
raise ErrorParsingFailed(bin_path)

@property
def has_nx(self) -> bool:
def has_nx(self) -> NX:
# Handle ELF binary with no program segments (e.g., Kernel modules)
# In this case, return True
if isinstance(self.bin, lief.ELF.Binary) and len(self.bin.segments) == 0:
return True
return NX.NA
elif self.bin.has_nx:
return NX.Yes
else:
return self.bin.has_nx
return NX.No

@property
def checksec_state(self) -> Union["ELFChecksecData", "PEChecksecData"]:
Expand Down
15 changes: 13 additions & 2 deletions checksec/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ def is_elf(filepath: Path) -> bool:
return lief.is_elf(str(filepath))


class FortifySource(Enum):
No = 1
Yes = 2
NA = 3


class RelroType(Enum):
No = 1
Partial = 2
Expand Down Expand Up @@ -215,7 +221,7 @@ def fortifiable(self) -> Optional[FrozenSet[str]]:

@property
def checksec_state(self) -> ELFChecksecData:
fortify_source = None
fortify_source = FortifySource.NA
fortified_count = None
fortifiable_count = None
score = None
Expand All @@ -232,7 +238,12 @@ def checksec_state(self) -> ELFChecksecData:
else:
score = (fortified_count * 100) / fortifiable_count
score = round(score)
fortify_source = True if fortified_count != 0 or fortifiable_count == 0 else False
if fortified_count != 0:
fortify_source = FortifySource.Yes
elif fortifiable_count == 0:
fortify_source = FortifySource.NA
else:
fortify_source = FortifySource.No
return ELFChecksecData(
relro=self.relro,
canary=self.has_canary,
Expand Down
32 changes: 18 additions & 14 deletions checksec/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from rich.progress import BarColumn, Progress, TextColumn
from rich.table import Table

from checksec.elf import ELFChecksecData, PIEType, RelroType
from checksec.binary import NX
from checksec.elf import ELFChecksecData, FortifySource, PIEType, RelroType
from checksec.pe import PEChecksecData

MACHINE_TYPES = Header.MACHINE_TYPES
Expand Down Expand Up @@ -145,10 +146,11 @@ def add_checksec_result(self, filepath: Path, checksec: Union[ELFChecksecData, P
if isinstance(checksec, ELFChecksecData):
row_res: List[str] = []
# display results
if not checksec.nx:
nx_res = "[red]No"
nx = checksec.nx
if nx == NX.No:
nx_res = f"[red]{nx.name}"
else:
nx_res = "[green]Yes"
nx_res = f"[green]{nx.name}"
row_res.append(nx_res)

pie = checksec.pie
Expand Down Expand Up @@ -197,13 +199,14 @@ def add_checksec_result(self, filepath: Path, checksec: Union[ELFChecksecData, P

# fortify results depend on having a Libc available
if self._libc_detected:
fortified_count = checksec.fortified
if checksec.fortify_source:
fortify_source_res = "[green]Yes"
fortify_source = checksec.fortify_source
if fortify_source == FortifySource.No:
fortify_source_res = f"[red]{fortify_source.name}"
else:
fortify_source_res = "[red]No"
fortify_source_res = f"[green]{fortify_source.name}"
row_res.append(fortify_source_res)

fortified_count = checksec.fortified
if fortified_count == 0:
fortified_res = "[red]No"
else:
Expand All @@ -227,10 +230,11 @@ def add_checksec_result(self, filepath: Path, checksec: Union[ELFChecksecData, P

self.table_elf.add_row(str(filepath), *row_res)
elif isinstance(checksec, PEChecksecData):
if not checksec.nx:
nx_res = "[red]No"
nx = checksec.nx
if nx == NX.No:
nx_res = f"[red]{nx.name}"
else:
nx_res = "[green]Yes"
nx_res = f"[green]{nx.name}"

if not checksec.canary:
canary_res = "[red]No"
Expand Down Expand Up @@ -340,19 +344,19 @@ def add_checksec_result(self, filepath: Path, checksec: Union[ELFChecksecData, P
self.data[str(filepath.resolve())] = {
"relro": checksec.relro.name,
"canary": checksec.canary,
"nx": checksec.nx,
"nx": checksec.nx.name,
"pie": checksec.pie.name,
"rpath": checksec.rpath,
"runpath": checksec.runpath,
"symbols": checksec.symbols,
"fortify_source": checksec.fortify_source,
"fortify_source": checksec.fortify_source.name,
"fortified": checksec.fortified,
"fortify-able": checksec.fortifiable,
"fortify_score": checksec.fortify_score,
}
elif isinstance(checksec, PEChecksecData):
self.data[str(filepath.resolve())] = {
"nx": checksec.nx,
"nx": checksec.nx.name,
"canary": checksec.canary,
Comment on lines 344 to 360
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change switches JSON output for nx (and fortify_source) from booleans to enum names ("Yes"/"No"/"NA"). The repository’s E2E tests currently assert these JSON fields are booleans (see tests/e2e/test_e2e_elf.py::test_bool_prop and tests/e2e/test_e2e_pe.py::test_bool_prop), so this will break the existing contract. Either keep booleans and represent “not applicable” as null (or a separate field), or update the tests (and any documented schema expectations) to match the new string values.

Copilot uses AI. Check for mistakes.
"aslr": checksec.aslr,
"dynamic_base": checksec.dynamic_base,
Expand Down
2 changes: 1 addition & 1 deletion tests/binaries
21 changes: 19 additions & 2 deletions tests/e2e/test_e2e_elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

import pytest

from checksec.elf import PIEType, RelroType
from checksec.binary import NX
from checksec.elf import FortifySource, PIEType, RelroType
from tests.conftest import run_checksec

ELF_BINARIES = Path(__file__).parent.parent / "binaries" / "elf"


@pytest.mark.parametrize("is_enabled", [False, True])
@pytest.mark.parametrize("prop", ["nx", "canary", "rpath", "runpath", "symbols", "fortify_source"])
@pytest.mark.parametrize("prop", ["canary", "rpath", "runpath", "symbols"])
def test_bool_prop(prop: str, is_enabled: bool):
"""Test that boolean prop is disabled/enabled"""
libc_path = ELF_BINARIES / "libc-2.27.so"
Expand All @@ -20,6 +21,22 @@ def test_bool_prop(prop: str, is_enabled: bool):
assert chk_data[str(bin_path)][prop] == is_enabled


@pytest.mark.parametrize("nx", list(NX))
def test_nx(nx: NX):
"""Test that nx is No/Yes/NA"""
bin_path = ELF_BINARIES / f"nx_{nx.name.lower()}"
chk_data = run_checksec(bin_path)
assert chk_data[str(bin_path)]["nx"] == nx.name


@pytest.mark.parametrize("fortify_source", list(FortifySource))
def test_fortify_source(fortify_source: FortifySource):
"""Test that fortify_source is No/Yes/NA"""
bin_path = ELF_BINARIES / f"fortify_source_{fortify_source.name.lower()}"
chk_data = run_checksec(bin_path)
assert chk_data[str(bin_path)]["fortify_source"] == fortify_source.name


@pytest.mark.parametrize("relro_type", list(RelroType))
def test_relro(relro_type: RelroType):
"""Test that relro type is No/Partial/Full/NA"""
Expand Down
12 changes: 11 additions & 1 deletion tests/e2e/test_e2e_pe.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest

from checksec.binary import NX
from tests.conftest import run_checksec

PE_BINARIES = Path(__file__).parent.parent / "binaries" / "pe"
Expand All @@ -13,7 +14,6 @@
@pytest.mark.parametrize(
"prop",
[
"nx",
"canary",
"dynamic_base",
"aslr",
Expand All @@ -30,3 +30,13 @@ def test_bool_prop(prop: str, is_enabled: bool):
bin_path = PE_BINARIES / f"{prop}_{'enabled' if is_enabled else 'disabled'}.exe"
chk_data = run_checksec(bin_path)
assert chk_data[str(bin_path)][prop] == is_enabled


@pytest.mark.parametrize("nx", list(NX))
def test_nx(nx: NX):
"""Test that nx is No/Yes"""
# NA not possible for PE (only for ELF)
if nx != NX.NA:
bin_path = PE_BINARIES / f"nx_{'enabled' if nx == NX.Yes else 'disabled'}.exe"
chk_data = run_checksec(bin_path)
assert chk_data[str(bin_path)]["nx"] == nx.name
Loading