Repository: kevoreilly/CAPEv2

Issue:
Submissions stay in pending when the right machinery isn't available
## About accounts on [capesandbox.com](https://capesandbox.com/)
* Issues isn't the way to ask for account activation. Ping capesandbox in [Twitter](https://twitter.com/capesandbox) with your username

## This is open source and you are getting __free__ support so be friendly!
* Free support from doomedraven ended - no whiskey, no support. For updates check the documentation.

# Prerequisites

Please answer the following questions for yourself before submitting an issue.

- [x] I am running the latest version
- [x] I did read the README!
- [x] I checked the documentation and found no answer
- [x] I checked to make sure that this issue has not already been filed
- [x] I'm reporting the issue to the correct repository (for multi-repository projects)
- [x] I have read and checked all configs (with all optional parts)


# Expected Behavior

Any task not capable of being serviced will fail analysis.

# Current Behavior

When the scheduler picks up tasks, it checks for unlocked machines by tags, architecture, and platform. If for example, there are no Linux machines available, an ELF sample will be stuck in the pending state forever.
is a bug. If it is not a bug, please remove the rest of this template.

## Steps to Reproduce

Please provide detailed steps for reproducing the issue.

1. setup machinery with Windows platforms only
2. submit a Linux sample

## Context

N/A; will have a PR up shortly

## Failure Logs

Please include any relevant log snippets or files here.



Discussion:

Resolving patch:
diff --git a/lib/cuckoo/core/database.py b/lib/cuckoo/core/database.py
--- a/lib/cuckoo/core/database.py
+++ b/lib/cuckoo/core/database.py
@@ -822,6 +822,25 @@ def is_relevant_machine_available(self, task: Task) -> bool:
             return True
         return False
 
+    @classlock
+    def is_serviceable(self, task: Task) -> bool:
+        """Checks if the task is serviceable.
+
+        This method is useful when there are tasks that will never be serviced
+        by any of the machines available. This allows callers to decide what to
+        do when tasks like this are created.
+
+        @return: boolean indicating if any machine could service the task in the future
+        """
+        task_archs = [tag.name for tag in task.tags if tag.name in ["x86", "x64"]]
+        task_tags = [tag.name for tag in task.tags if tag.name not in task_archs]
+        relevant_machines = self.list_machines(
+            label=task.machine, platform=task.platform, tags=task_tags, arch=task_archs
+        )
+        if len(relevant_machines) > 0:
+            return True
+        return False
+
     @classlock
     def fetch_task(self, categories: list = []):
         """Fetches a task waiting to be processed and locks it for running.
diff --git a/lib/cuckoo/core/scheduler.py b/lib/cuckoo/core/scheduler.py
--- a/lib/cuckoo/core/scheduler.py
+++ b/lib/cuckoo/core/scheduler.py
@@ -24,7 +24,13 @@
 from lib.cuckoo.common.integrations.parse_pe import PortableExecutable
 from lib.cuckoo.common.objects import File
 from lib.cuckoo.common.utils import convert_to_printable, create_folder, free_space_monitor, get_memdump_path, load_categories
-from lib.cuckoo.core.database import TASK_COMPLETED, TASK_PENDING, Database, Task
+from lib.cuckoo.core.database import (
+    TASK_COMPLETED,
+    TASK_FAILED_ANALYSIS,
+    TASK_PENDING,
+    Database,
+    Task,
+)
 from lib.cuckoo.core.guest import GuestManager
 from lib.cuckoo.core.log import task_log_stop
 from lib.cuckoo.core.plugins import RunAuxiliary, list_plugins
@@ -856,6 +862,13 @@ def start(self):
                     for task in self.db.list_tasks(
                         status=TASK_PENDING, order_by=(Task.priority.desc(), Task.added_on), options_not_like="node="
                     ):
+                        # Can this task ever be serviced?
+                        if not self.db.is_serviceable(task):
+                            if self.cfg.cuckoo.fail_unserviceable:
+                                log.debug("Task #%s: Failing unserviceable task", task.id)
+                                self.db.set_status(task.id, TASK_FAILED_ANALYSIS)
+                                continue
+                            log.debug("Task #%s: Unserviceable task", task.id)
                         relevant_machine_is_available = self.db.is_relevant_machine_available(task)
                         if relevant_machine_is_available:
                             break


Test patch:
diff --git a/tests/test_database.py b/tests/test_database.py
--- a/tests/test_database.py
+++ b/tests/test_database.py
@@ -8,7 +8,7 @@
 from tempfile import NamedTemporaryFile
 
 from lib.cuckoo.common.utils import store_temp_file
-from lib.cuckoo.core.database import Database, Task
+from lib.cuckoo.core.database import Database, Task, Tag
 
 
 class TestDatabaseEngine:
@@ -203,3 +203,41 @@ def test_add_machine(self):
             "tags": ["tag1tag2"],
             "arch": "x64",
         }
+
+    def test_is_serviceable(self):
+        self.d.add_machine(
+            name="win10-x64-1",
+            label="label",
+            ip="1.2.3.4",
+            platform="windows",
+            tags="tag1",
+            interface="int0",
+            snapshot="snap0",
+            resultserver_ip="5.6.7.8",
+            resultserver_port=2043,
+            arch="x64",
+        )
+        task = Task()
+        task.platform = "windows"
+        task.tags = [Tag("tag1")]
+        # tasks matching the available machines are serviceable
+        assert self.d.is_serviceable(task)
+
+    def test_is_not_serviceable(self):
+        self.d.add_machine(
+            name="win10-x64-1",
+            label="label",
+            ip="1.2.3.4",
+            platform="windows",
+            tags="tag1",
+            interface="int0",
+            snapshot="snap0",
+            resultserver_ip="5.6.7.8",
+            resultserver_port=2043,
+            arch="x64",
+        )
+        task = Task()
+        task.platform = "linux"
+        task.tags = [Tag("tag1")]
+        # tasks not matching the available machines aren't serviceable
+        assert not self.d.is_serviceable(task)
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -128,6 +128,7 @@ def test_init(self):
             "max_analysis_count": 0,
             "freespace_processing": 15000,
             "periodic_log": False,
+            "fail_unserviceable": True,
         }
 
         assert analysis_man.task.id == 1234