diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..a5b945a1f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# the following files and matching directories will not be copied to the docker container +Dockerfile* +build/* +run*.sh diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..0d1a0d9ef --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,144 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +xSTUDIO is a professional media playback and review application designed for the film and TV post-production industries, particularly VFX and feature animation. It features a high-performance playback engine built with C++ and Python APIs for pipeline integration and customization. + +## Core Architecture + +xSTUDIO is built on a modular, actor-based architecture using the C++ Actor Framework (CAF): + +- **Actor System**: The core is built around CAF actors that communicate via message passing +- **Registry System**: Components are registered in named registries (e.g., `global_registry`, `plugin_manager_registry`, `media_reader_registry`) +- **Plugin Architecture**: Extensible plugin system supporting multiple plugin types (colour ops, media readers, data sources, viewport overlays, etc.) +- **Session Management**: Sessions contain playlists, timelines, and media sources with hierarchical organization +- **Media Pipeline**: Handles media reading, caching, metadata extraction, and playback +- **UI Framework**: Qt5/QML-based interface with OpenGL viewport rendering + +### Key Components + +- **Global Actor** (`src/global/`): Central coordinator and message router +- **Studio Actor** (`src/studio/`): Top-level application container +- **Session System** (`src/session/`): Project/session management +- **Media System** (`src/media/`): Media sources, streams, and metadata handling +- **Playhead** (`src/playhead/`): Playback control and timeline navigation +- **Plugin Manager** (`src/plugin_manager/`): Dynamic plugin loading and management +- **Viewport** (`src/ui/viewport/`): OpenGL-based media display and rendering +- **Python Integration** (`src/embedded_python/`, `src/python_module/`): Python API and scripting + +## Development Commands + +### Building +```bash +# Configure build with CMake +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release + +# Build (adjust -j based on CPU cores) +make -j$(nproc) + +# Build specific target +make xstudio +``` + +### Testing +```bash +# Build with tests enabled +cmake .. -DBUILD_TESTING=ON + +# Run C++ tests +make test + +# Run Python tests (requires built xstudio) +cd python/test +pytest +``` + +### Code Quality +```bash +# Format C++ code +ninja clangformat # if ENABLE_CLANG_FORMAT=ON + +# Static analysis +ninja clang-tidy # if ENABLE_CLANG_TIDY=ON +``` + +### Running +```bash +# Start xstudio +./build/bin/xstudio.bin + +# Start with specific session +./build/bin/xstudio.bin --session "session_name" + +# Headless mode for testing +./build/bin/xstudio.bin -e -n --log-file xstudio.log + +# Python control scripts +xstudio-control # CLI control interface +xstudio-inject # Inject commands into running instance +``` + +## File Organization + +### Core Libraries (`src/`) +- `atoms.hpp`: CAF message atom definitions (central to actor communication) +- `*/src/`: Implementation files for each component +- `*/test/`: Unit tests for each component +- `plugin/`: Plugin implementations organized by type + +### UI Components (`src/ui/`) +- `qml/`: QML UI components and models +- `opengl/`: OpenGL rendering and shaders +- `viewport/`: Main viewport implementation + +### Python Integration (`python/`) +- `src/xstudio/`: Python API modules +- `test/`: Python test suite using pytest +- Uses pybind11 for C++/Python binding + +### Configuration (`share/`) +- `preference/`: JSON preference files for core and plugins +- `fonts/`: Application fonts +- `snippets/`: Code/configuration snippets + +## Plugin Development + +Plugins inherit from base classes in `include/xstudio/plugin_manager/plugin_base.hpp`: + +- **ColourOp**: Image processing operations +- **MediaReader**: Media file format support +- **DataSource**: External data integration (e.g., Shotgun) +- **ViewportOverlay**: HUD and overlay elements +- **MediaHook**: Pipeline integration hooks + +Plugin registration uses factory pattern and CMake auto-discovery. + +## Testing Strategy + +- **C++ Tests**: Unit tests using framework in `test/` directories +- **Python Tests**: pytest-based tests in `python/test/` +- **Integration Tests**: End-to-end tests via Python API +- **Test Resources**: Sample media files in `test_resource/` + +## Key Conventions + +- **Message Passing**: Use CAF atoms for inter-actor communication +- **UUID Usage**: UUIDs identify sessions, media, playlists throughout system +- **JSON Configuration**: Preferences and settings stored as JSON +- **Frame-based Operations**: Timeline operations use frame-based addressing +- **Plugin Lifecycle**: Plugins loaded dynamically at startup and managed centrally + +## Common Patterns + +- Actor spawning via `spawn_actor_in_group()` with registry registration +- Asynchronous operations using CAF's `request().then()` pattern +- UI updates via Qt signal/slot mechanism bridged to actor messages +- Media metadata cached and shared across components +- OpenGL rendering with shader program abstractions + +## Build Dependencies + +See `docs/build_guides/` for platform-specific dependency installation. Key dependencies include Qt5, OpenEXR, CAF, pybind11, OpenGL, and various media libraries for format support. \ No newline at end of file diff --git a/CODE_REVIEW_CLAUDE.md b/CODE_REVIEW_CLAUDE.md new file mode 100644 index 000000000..c5de6e83f --- /dev/null +++ b/CODE_REVIEW_CLAUDE.md @@ -0,0 +1,281 @@ +# Comprehensive Code Review Report - xSTUDIO + +**Review Date:** August 1, 2025 +**Reviewer:** Claude Code +**Scope:** Security vulnerabilities, memory management, and code quality issues + +## Executive Summary + +This comprehensive review identified **15 critical security vulnerabilities** and **20+ additional issues** across the xSTUDIO codebase. The most severe issues involve buffer overflows, code injection vulnerabilities, and unsafe memory management practices. While the codebase demonstrates good architectural patterns using the CAF actor framework, several critical security gaps require immediate attention. + +## Critical Issues (Priority 1 - Fix Immediately) + +### 1. **Buffer Overflow in String Operations** +**Severity: CRITICAL** | **Impact: HIGH** | **Fix Cost: LOW** + +**Files:** +- `src/session/src/session_actor.cpp:1048` +- `src/ui/opengl/src/gl_debug_utils.cpp:27` +- `src/plugin/hud/pixel_probe/src/pixel_probe.cpp:275` + +**Issue:** Multiple instances of `sprintf()` without bounds checking +```cpp +sprintf(buf.data(), path.c_str(), idx); // 4096 byte buffer, unchecked format +sprintf(nm.data(), "/user_data/.tmp/xstudio_viewport.%04d.exr", fnum++); // 2048 byte buffer +sprintf(buf, "%d", info[i].code_value); // 128 byte buffer +``` + +**Risk:** Buffer overflow leading to stack corruption and potential RCE +**Fix:** Replace with `snprintf()` with explicit size limits or use safer C++ string formatting + +### 2. **Python Code Injection Vulnerability** +**Severity: CRITICAL** | **Impact: HIGH** | **Fix Cost: MEDIUM** + +**File:** `src/embedded_python/src/embedded_python.cpp:145` + +**Issue:** Direct execution of user-controlled Python code without sanitization +```cpp +void EmbeddedPython::exec(const std::string &pystring) const { + py::exec(pystring); +} +``` + +**Risk:** Arbitrary code execution if user input reaches these functions +**Fix:** Implement input sanitization, use restricted execution contexts, validate against allowed operations + +### 3. **Unsafe Dynamic Library Loading** +**Severity: HIGH** | **Impact: HIGH** | **Fix Cost: MEDIUM** + +**File:** `src/plugin_manager/src/plugin_manager.cpp:67-105` + +**Issue:** `dlopen()` and `LoadLibraryA()` with user-controlled paths +```cpp +handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); // No path validation +``` + +**Risk:** Loading malicious libraries, DLL hijacking attacks +**Fix:** Validate plugin paths against whitelist, use absolute paths, implement code signing verification + +### 4. **URI Decode Buffer Overflow** +**Severity: HIGH** | **Impact: MEDIUM** | **Fix Cost: LOW** + +**File:** `src/utility/src/helpers.cpp:320-332` + +**Issue:** URI decode with `sscanf()` and inadequate bounds checking +```cpp +sscanf(hex.c_str(), "%x", &c); // No validation of hex input +``` + +**Risk:** Buffer overflow during URL processing +**Fix:** Add input validation, use safer parsing methods with explicit bounds + +### 5. **Thread Safety Issues in Memory Operations** +**Severity: HIGH** | **Impact: MEDIUM** | **Fix Cost: MEDIUM** + +**Files:** +- `src/ui/opengl/src/texture.cpp:254-265` +- `src/launch/xstudio/src/xstudio.cpp:107-121` + +**Issue:** Concurrent memory access without proper synchronization +```cpp +// Multi-threaded memcpy operations without synchronization +memcpy(buffer, source, size); // Called from multiple threads +``` + +**Risk:** Race conditions, memory corruption, crashes +**Fix:** Add proper synchronization primitives, use atomic operations where appropriate + +## High Priority Issues (Priority 2 - Fix Soon) + +### 6. **Memory Management Issues** +**Severity: MEDIUM-HIGH** | **Impact: MEDIUM** | **Fix Cost: MEDIUM** + +**File:** `src/launch/xstudio/src/xstudio.cpp:1015-1034` + +**Issue:** Raw pointer allocations without guaranteed cleanup +```cpp +new CafSystemObject(&app, system); // Multiple raw new operations +new ThumbnailProvider; +new Helpers(&engine); +``` + +**Risk:** Memory leaks, use-after-free vulnerabilities +**Fix:** Use smart pointers, ensure proper RAII patterns + +### 7. **OpenGL Resource Management** +**Severity: MEDIUM** | **Impact: MEDIUM** | **Fix Cost: LOW** + +**File:** `src/pyside2_module/src/threaded_viewport.cpp:119` + +**Issue:** Missing error checking after `glMapBuffer()` +```cpp +void *mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); +// No null check before use +``` + +**Risk:** GPU memory corruption, application crashes +**Fix:** Add error checking for all OpenGL operations, implement RAII for resources + +### 8. **HTTP Request Validation Missing** +**Severity: MEDIUM** | **Impact: MEDIUM** | **Fix Cost: LOW** + +**File:** `src/http_client/src/http_client_actor.cpp:76-97` + +**Issue:** No URL validation for HTTP requests +```cpp +// No validation of scheme_host_port parameter +httplib::Client cli(scheme_host_port.c_str()); +``` + +**Risk:** Server-Side Request Forgery (SSRF) attacks +**Fix:** Implement URL validation, whitelist allowed hosts, add proper SSL verification + +### 9. **Image Processing Buffer Issues** +**Severity: MEDIUM** | **Impact: MEDIUM** | **Fix Cost: LOW** + +**File:** `src/thumbnail/src/thumbnail.cpp:161,280,431,542` + +**Issue:** `memcpy()` operations without bounds validation +```cpp +memcpy(squashed, inBuffer, inWidth * inHeight * nchans * sizeof(float)); +// No validation that destination buffer is large enough +``` + +**Risk:** Buffer overflows during image processing +**Fix:** Add buffer size validation before copy operations + +### 10. **Integer Overflow Vulnerabilities** +**Severity: MEDIUM** | **Impact: LOW** | **Fix Cost: LOW** + +**File:** `src/utility/src/edit_list.cpp:571` + +**Issue:** Unsafe cast from `size_t` to `int` +```cpp +int result = static_cast(large_size_t_value); // Potential truncation +``` + +**Risk:** Logic errors with large datasets, potential integer overflow +**Fix:** Use consistent integer types, add overflow checking + +## Medium Priority Issues (Priority 3 - Address in Next Release) + +### 11. **PPM File Parser Vulnerabilities** +**Severity: MEDIUM** | **Impact: LOW** | **Fix Cost: LOW** + +**File:** `src/plugin/media_reader/ppm/src/ppm.cpp:67-100` + +**Issue:** Insufficient validation of PPM file headers +```cpp +std::getline(inp, line); +dimensions >> width >> height; // No bounds checking on dimensions +``` + +**Risk:** Malformed files could cause memory issues +**Fix:** Add validation for image dimensions and file format + +### 12. **Logging System Thread Safety** +**Severity: LOW-MEDIUM** | **Impact: LOW** | **Fix Cost: LOW** + +**Files:** Various logging calls throughout codebase + +**Issue:** Potential race conditions in logging system +**Risk:** Log corruption, performance degradation +**Fix:** Ensure thread-safe logging configuration + +### 13. **Plugin Lifecycle Management** +**Severity: LOW** | **Impact: LOW** | **Fix Cost: MEDIUM** + +**File:** Plugin management system + +**Issue:** Limited validation of plugin state transitions +**Risk:** Plugin instability, resource leaks +**Fix:** Implement comprehensive plugin lifecycle validation + +## Architecture and Design Issues + +### 14. **Error Handling Inconsistencies** +**Severity: MEDIUM** | **Impact: MEDIUM** | **Fix Cost: MEDIUM** + +Multiple files show inconsistent error handling patterns, particularly around: +- CAF actor message handling +- File I/O operations +- Memory allocation failures + +**Fix:** Standardize error handling patterns, implement comprehensive error recovery + +### 15. **Resource Cleanup in Destructors** +**Severity: MEDIUM** | **Impact: MEDIUM** | **Fix Cost: MEDIUM** + +Several classes lack proper cleanup in destructors, particularly for: +- OpenGL resources +- Network connections +- File handles + +**Fix:** Implement RAII patterns consistently, add proper destructors + +## Positive Findings + +The codebase demonstrates several strong architectural decisions: + +1. **CAF Actor Framework Usage**: Proper message-passing architecture reduces many concurrency issues +2. **Memory Recycling**: Sophisticated image buffer recycling system for performance +3. **Plugin Architecture**: Well-designed plugin system with proper abstractions +4. **Modern C++ Features**: Good use of smart pointers in newer code sections +5. **Error Handling**: Comprehensive error types and exception handling in core areas + +## Recommendations by Priority + +### Immediate Actions (This Week) +1. Replace all `sprintf` calls with `snprintf` +2. Add input validation for Python execution paths +3. Implement plugin path validation and code signing +4. Fix URI decode buffer overflow + +### Short-term (This Month) +1. Implement proper synchronization for multi-threaded operations +2. Add comprehensive error checking for all OpenGL operations +3. Implement HTTP request validation and SSRF protection +4. Add buffer bounds checking for image processing + +### Long-term (Next Quarter) +1. Comprehensive security audit of all user input paths +2. Implementation of memory-safe alternatives for critical components +3. Static analysis integration in CI/CD pipeline +4. Security-focused testing framework + +## Testing Recommendations + +1. **Fuzzing Tests**: Implement fuzzing for all file format parsers +2. **Memory Testing**: Add Valgrind/AddressSanitizer to CI pipeline +3. **Security Testing**: Implement security-focused unit tests +4. **Load Testing**: Stress test multi-threaded components +5. **Plugin Testing**: Comprehensive plugin loading/unloading tests + +## Risk Assessment Summary + +| Category | Count | Risk Level | +|----------|--------|------------| +| Critical Vulnerabilities | 5 | Very High | +| High Priority Issues | 5 | High | +| Medium Priority Issues | 8 | Medium | +| Low Priority Issues | 7 | Low | + +**Overall Risk Rating: HIGH** - Immediate action required for critical vulnerabilities + +## Cost-Benefit Analysis + +| Fix Category | Development Time | Risk Reduction | Priority | +|--------------|------------------|----------------|----------| +| Buffer Overflow Fixes | 2-3 days | Very High | Critical | +| Python Injection Fix | 5-7 days | Very High | Critical | +| Plugin Security | 7-10 days | High | High | +| Memory Management | 10-14 days | High | High | +| OpenGL Error Handling | 3-5 days | Medium | Medium | + +## Conclusion + +While xSTUDIO demonstrates solid architectural foundations, the identified security vulnerabilities pose significant risks that require immediate attention. The buffer overflow and code injection issues are particularly critical and should be addressed before any production deployment. The good news is that most critical issues have relatively low fix costs and can be addressed quickly with focused effort. + +The codebase shows evidence of security awareness in some areas but lacks consistent application of secure coding practices throughout. Implementing a comprehensive security review process and static analysis tools will help prevent similar issues in the future. + +**Recommendation:** Address all Critical and High priority issues before the next release, and establish a security-focused development process going forward. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..c7cd9b384 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,169 @@ +FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04 as devel + +ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Europe/London + +RUN apt-get update && apt-get install -y --no-install-recommends \ + wget build-essential git cmake + +RUN apt-get install -y python3-pip \ + doxygen sphinx-common sphinx-rtd-theme-common python3-breathe \ + python-is-python3 pybind11-dev libpython3-dev \ + libspdlog-dev libfmt-dev libssl-dev zlib1g-dev libasound2-dev nlohmann-json3-dev uuid-dev \ + libglu1-mesa-dev freeglut3-dev mesa-common-dev libglew-dev libfreetype-dev \ + libjpeg-dev libpulse-dev \ + yasm nasm libfdk-aac-dev libfdk-aac2 libmp3lame-dev libopus-dev libvpx-dev libx265-dev libx264-dev \ + qttools5-dev qtbase5-dev qt5-qmake qtdeclarative5-dev qtquickcontrols2-5-dev \ + qml-module-qtquick* qml-module-qt-labs-* && \ + pip install sphinx_rtd_theme opentimelineio + +FROM devel as openexr +ARG JOBS=4 +WORKDIR src +RUN git clone -b RB-3.1 --single-branch https://github.com/AcademySoftwareFoundation/openexr.git && \ + cd openexr && \ + mkdir build + +RUN cd openexr/build && \ + cmake .. -DOPENEXR_INSTALL_TOOLS=Off -DBUILD_TESTING=Off && \ + make -j $JOBS && \ + make install + +FROM devel as actor-framework +ARG JOBS=4 +WORKDIR src +RUN wget -q https://github.com/actor-framework/actor-framework/archive/refs/tags/0.18.4.tar.gz && \ + tar -xf 0.18.4.tar.gz && \ + cd actor-framework-0.18.4 && \ + ./configure + +RUN cd actor-framework-0.18.4/build make -j $JOBS && \ + make install + +FROM devel as ocio +WORKDIR src +ARG JOBS=4 +RUN wget -q https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.0.tar.gz && \ + tar -xf v2.2.0.tar.gz && \ + cd OpenColorIO-2.2.0/ && \ + mkdir build + +RUN cd OpenColorIO-2.2.0/build && \ + cmake -DOCIO_BUILD_APPS=OFF -DOCIO_BUILD_TESTS=OFF -DOCIO_BUILD_GPU_TESTS=OFF ../ && \ + make -j $JOBS && \ + make install + +FROM devel as nlohmann +WORKDIR src +ARG JOBS=4 +RUN wget -q https://github.com/nlohmann/json/archive/refs/tags/v3.7.3.tar.gz +RUN tar -xf v3.7.3.tar.gz && \ + cd json-3.7.3 && \ + mkdir build +RUN cd json-3.7.3/build && \ + cmake .. -DJSON_BuildTests=Off && \ + make -j $JOBS && \ + make install + +FROM devel as ffmpeg +WORKDIR src +ARG JOBS=4 +RUN wget -q https://ffmpeg.org/releases/ffmpeg-5.1.tar.bz2 && \ + tar -xf ffmpeg-5.1.tar.bz2 && \ + cd ffmpeg-5.1/ && \ + export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH && \ + ./configure --extra-libs=-lpthread --extra-libs=-lm --enable-gpl --enable-libfdk_aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvpx --enable-libx264 --enable-libx265 --enable-shared --enable-nonfree && \ + make -j $JOBS && \ + make install + +#FROM bitnami/python:3.8 +#FROM ubuntu:22.04 +#FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04 +FROM devel as main + +LABEL maintainer="ling@techarge.co.uk" + + + +#RUN apt-get update && apt-get install -y --no-install-recommends \ + #wget \ + #build-essential + +#RUN apt-get install -y python3-pip \ + #doxygen sphinx-common sphinx-rtd-theme-common python3-breathe \ + #python-is-python3 pybind11-dev libpython3-dev \ + #libspdlog-dev libfmt-dev libssl-dev zlib1g-dev libasound2-dev nlohmann-json3-dev uuid-dev \ + #libglu1-mesa-dev freeglut3-dev mesa-common-dev libglew-dev libfreetype-dev \ + #libjpeg-dev libpulse-dev \ + #yasm nasm libfdk-aac-dev libfdk-aac2 libmp3lame-dev libopus-dev libvpx-dev libx265-dev libx264-dev \ + #qttools5-dev qtbase5-dev qt5-qmake qtdeclarative5-dev qtquickcontrols2-5-dev \ + #qml-module-qtquick* qml-module-qt-labs-* && \ + #pip install sphinx_rtd_theme opentimelineio + +# install glvnd to display OpenGL on host +RUN apt-get install -qqy libglvnd0 libgl1 libglx0 libegl1 libglvnd-dev libgl1-mesa-dev libegl1-mesa-dev +# if using jetson(aka a100?) then install this too +#RUN apt-get install -qqy libgles2 libgles2-mesa-dev + +# Xlibs +RUN apt-get install -qqy libxext6 libx11-6 +# optional OpenGL libs +RUN apt-get install -qqy freeglut3 freeglut3-dev + + +ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute + + + + +# OpenEXR +WORKDIR src +#ENV JOBS=`nproc` +ENV JOBS=4 +#RUN git clone -b RB-3.1 --single-branch https://github.com/AcademySoftwareFoundation/openexr.git && \ +# cd openexr/ && \ +# mkdir build && \ +# cd build && \ +# cmake .. -DOPENEXR_INSTALL_TOOLS=Off -DBUILD_TESTING=Off && \ +# make -j $JOBS && \ +# make install + + + +# TODO: copy build artifacts to main container +COPY --from=nlohmann /usr/local/lib /usr/local/lib/ +COPY --from=nlohmann /usr/local/bin /usr/local/bin/ +COPY --from=ocio /usr/local/lib /usr/local/lib/ +COPY --from=ocio /usr/local/bin /usr/local/bin/ +COPY --from=actor-framework /usr/local/lib /usr/local/lib/ +COPY --from=actor-framework /usr/local/bin /usr/local/bin/ +COPY --from=openexr /usr/local/lib /usr/local/lib/ +COPY --from=openexr /usr/local/bin /usr/local/bin/ +COPY --from=ffmpeg /usr/local/lib /usr/local/lib/ +COPY --from=ffmpeg /usr/local/bin /usr/local/bin/ + +# xStudio +ENV PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig:/usr/local/lib64/pkgconfig +ARG JOBS=4 +WORKDIR xstudio +COPY . . +RUN mkdir build && \ + cd build && \ + cmake .. -DBUILD_DOCS=Off && \ + make -j $JOBS + +ENV QV4_FORCE_INTERPRETER=1 +ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64 +ENV PYTHONPATH=./bin/python/lib/python3.10/site-packages:/home/xstudio/.local/lib/python3.10/site-packages + +RUN apt-get install -qqy x11-apps + +# TODO: correct port number to expose +EXPOSE 8000 + +ENV DISPLAY=:0 + +CMD ./build/bin/xstudio.bin + diff --git a/README.md b/README.md index 124f666e4..3d4b5214f 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,771 @@ -# Welcome to xSTUDIO +# xSTUDIO - Professional Media Review and Playback Application -xSTUDIO is a media playback and review application designed for professionals working in the film and TV post production industries, particularly the Visual Effects and Feature Animation sectors. xSTUDIO is focused on providing an intuitive, easy to use interface with a high performance playback engine at its core and C++ and Python APIs for pipeline integration and customisation for total flexibility. +
-## Building xSTUDIO +![xSTUDIO Logo](ui/icons/xstudio_fileicon_256.png) -This release of xSTUDIO can be built on various Linux flavours and Windows 10 and 11. MacOS compatibility is not available yet but this work is on the roadmap for 2024. +**A high-performance media playback and review application for film and TV post-production** -We provide comprehensive build steps for 4 of the most popular distributions. +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) +[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](#building-xstudio) +[![C++](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://isocpp.org/) +[![Python](https://img.shields.io/badge/Python-3.8%2B-blue.svg)](https://python.org/) -### Building xSTUDIO for Linux +
-* [CentOS 7](docs/build_guides/centos_7.md) -* [Rocky Linux 9.1](docs/build_guides/rocky_linux_9_1.md) -* [Ubuntu 22.04](docs/build_guides/ubuntu_22_04.md) +## 🎬 What is xSTUDIO? -### Building xSTUDIO for Windows +xSTUDIO is a cutting-edge media playback and review application designed specifically for professionals in the **Visual Effects**, **Feature Animation**, and **Film/TV Post-Production** industries. Built with performance at its core, xSTUDIO provides an intuitive interface backed by a robust C++ engine and comprehensive Python APIs for seamless pipeline integration. -* [Windows](docs/build_guides/windows.md) +### 🌟 Key Features -### Building xSTUDIO for MacOS +- **🚀 High-Performance Playback Engine** - Optimized for large media files and high-resolution formats +- **🎨 Professional Review Tools** - Advanced color management, annotations, and comparison tools +- **📊 Timeline & Playlist Management** - Sophisticated media organization and playlist creation +- **🔌 Extensible Plugin Architecture** - Custom plugins for specialized workflows +- **🐍 Python API Integration** - Complete Python SDK for pipeline automation +- **🌐 Multi-Platform Support** - Linux, Windows (macOS coming soon) +- **👥 Collaborative Features** - Session sharing and remote control capabilities +- **📝 Comprehensive Annotation System** - Drawing tools, notes, and markup features -MacOS compatibility is not yet available. Watch this space! +### 🎯 Target Users -### Documentation Note +- **VFX Artists & Supervisors** - Review shots, compare versions, annotate feedback +- **Animation Directors** - Timeline scrubbing, frame-by-frame analysis +- **Post-Production Teams** - Collaborative review sessions, playlist management +- **Pipeline Engineers** - Python API integration, custom tool development -Note that the xSTUDIO user guide is built with Sphinx using the Read-The-Docs theme. The package dependencies for building the docs are somewhat onerous to install and as such we have ommitted these steps from the instructions and instead recommend that you turn off the docs build. Instead, we include the fully built docs (as html pages) as part of this repo and building xSTUDIO will install these pages so that they can be loaded into your browser via the Help menu in the main UI. +## 🏗️ Architecture Overview + +xSTUDIO is built on a sophisticated **actor-based architecture** using the C++ Actor Framework (CAF), providing excellent scalability, thread safety, and modularity. + +### Core Architecture Diagram + +```mermaid +graph TB + subgraph "User Interface Layer" + QML[QML/Qt Interface] + VP[OpenGL Viewport] + UI[UI Components] + end + + subgraph "Application Layer" + GA[Global Actor
Central Coordinator] + SA[Studio Actor
Top-level Container] + SESS[Session Actor
Project Management] + end + + subgraph "Media Pipeline" + PH[Playhead Actor
Playback Control] + PL[Playlist Actor
Media Organization] + TL[Timeline Actor
Sequence Management] + MR[Media Reader
File I/O] + MC[Media Cache
Buffer Management] + end + + subgraph "Plugin System" + PM[Plugin Manager] + CO[Color Operations] + VO[Viewport Overlays] + DS[Data Sources] + MH[Media Hooks] + end + + subgraph "External Integration" + PY[Python API] + HTTP[HTTP Client] + SG[Shotgun Integration] + SYNC[Sync Gateway] + end + + QML --> GA + VP --> PH + UI --> SA + + GA --> SA + SA --> SESS + SESS --> PL + SESS --> TL + PL --> PH + TL --> PH + PH --> MR + MR --> MC + + GA --> PM + PM --> CO + PM --> VO + PM --> DS + PM --> MH + + GA --> PY + GA --> HTTP + HTTP --> SG + GA --> SYNC + + style GA fill:#e1f5fe + style SA fill:#f3e5f5 + style SESS fill:#e8f5e8 + style PH fill:#fff3e0 + style PM fill:#fce4ec +``` + +### Actor Communication Pattern + +```mermaid +sequenceDiagram + participant UI as QML Interface + participant GA as Global Actor + participant SA as Studio Actor + participant SESS as Session Actor + participant PH as Playhead Actor + participant MR as Media Reader + + UI->>GA: User Action (Play) + GA->>SA: Forward Command + SA->>SESS: Session Control + SESS->>PH: Playback Request + PH->>MR: Frame Request + MR-->>PH: Image Buffer + PH-->>SESS: Frame Ready + SESS-->>SA: Update State + SA-->>GA: Status Update + GA-->>UI: UI Refresh + + Note over GA, MR: All communication via
asynchronous messages +``` + +### Plugin Architecture + +```mermaid +graph LR + subgraph "Plugin Types" + CO[Color Operations
• Grading
• LUTs
• Effects] + MR[Media Readers
• FFmpeg
• OpenEXR
• Custom Formats] + VO[Viewport Overlays
• HUD Elements
• Annotations
• Masks] + DS[Data Sources
• Shotgun
• Database
• File Systems] + MH[Media Hooks
• Pipeline Integration
• Custom Processing] + end + + subgraph "Plugin System" + PM[Plugin Manager] + PF[Plugin Factory] + PL[Plugin Loader] + end + + subgraph "Core System" + VP[Viewport] + MP[Media Pipeline] + UI[User Interface] + end + + PM --> PF + PM --> PL + PF --> CO + PF --> MR + PF --> VO + PF --> DS + PF --> MH + + VP --> CO + VP --> VO + MP --> MR + MP --> MH + UI --> DS + + style PM fill:#e3f2fd + style VP fill:#f1f8e9 + style MP fill:#fce4ec +``` + +## 🚀 Quick Start Guide + +### Prerequisites + +**System Requirements:** +- **Linux**: Ubuntu 22.04+, CentOS 7+, Rocky Linux 9.1+ +- **Windows**: Windows 10/11 with Visual Studio 2019+ +- **Hardware**: OpenGL 3.3+, 8GB+ RAM recommended +- **Dependencies**: CMake 3.12+, Qt5.15+, Python 3.8+ + +### Building from Source + +#### 1. Clone the Repository +```bash +git clone https://github.com/AcademySoftwareFoundation/xstudio.git +cd xstudio +``` + +#### 2. Install Dependencies + +**Ubuntu 22.04:** +```bash +sudo apt install build-essential cmake git python3-pip +sudo apt install qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev +sudo apt install libspdlog-dev libfmt-dev libssl-dev zlib1g-dev +sudo apt install libglu1-mesa-dev libglew-dev libfreetype-dev +sudo apt install libjpeg-dev libpulse-dev nlohmann-json3-dev +``` + +**See detailed platform-specific instructions:** +- [Ubuntu 22.04](docs/build_guides/ubuntu_22_04.md) +- [CentOS 7](docs/build_guides/centos_7.md) +- [Rocky Linux 9.1](docs/build_guides/rocky_linux_9_1.md) +- [Windows](docs/build_guides/windows.md) + +#### 3. Build xSTUDIO +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) +``` + +#### 4. Run xSTUDIO +```bash +./bin/xstudio.bin +``` + +### Docker Setup + +For containerized development: + +```bash +# Build Docker image +docker build -t xstudio . + +# Enable X11 forwarding (Linux) +sudo xhost +local:root + +# Run container with GPU support +docker run --rm -it --gpus all \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -e DISPLAY=$DISPLAY \ + xstudio bash + +# Launch xSTUDIO inside container +./build/bin/xstudio.bin +``` + +## 👨‍💻 Developer Guide + +### Project Structure + +``` +xstudio/ +├── src/ # Core C++ source code +│ ├── global/ # Global actor and system coordination +│ ├── studio/ # Top-level application container +│ ├── session/ # Project/session management +│ ├── media/ # Media handling and sources +│ ├── playhead/ # Playback control and timeline +│ ├── playlist/ # Media organization +│ ├── ui/ # User interface components +│ │ ├── qml/ # QML UI components +│ │ ├── opengl/ # OpenGL rendering +│ │ └── viewport/ # Main viewport implementation +│ ├── plugin/ # Plugin implementations +│ │ ├── colour_op/ # Color operation plugins +│ │ ├── media_reader/ # Media format readers +│ │ ├── data_source/ # External data integration +│ │ └── viewport_overlay/ # HUD and overlay plugins +│ └── utility/ # Core utilities and helpers +├── include/xstudio/ # Public header files +├── python/ # Python API and bindings +├── ui/qml/ # QML interface files +├── share/ # Configuration and resources +└── docs/ # Documentation +``` + +### Key Components Deep Dive + +#### 1. Actor System +The core of xSTUDIO is built on the **C++ Actor Framework (CAF)**: + +```cpp +// Example: Creating an actor +class MyActor : public caf::event_based_actor { +public: + MyActor(caf::actor_config& cfg) : caf::event_based_actor(cfg) { + behavior_.assign( + [=](play_atom) { + // Handle play command + send(playhead_, play_atom_v); + } + ); + } + + caf::behavior make_behavior() override { return behavior_; } +private: + caf::behavior behavior_; + caf::actor playhead_; +}; +``` + +#### 2. Media Pipeline +Media processing follows this flow: + +```mermaid +graph LR + A[Media File] --> B[Media Reader Plugin] + B --> C[Image Buffer] + C --> D[Color Pipeline] + D --> E[Cache] + E --> F[Viewport Renderer] + F --> G[Display] + + style C fill:#e1f5fe + style D fill:#f3e5f5 + style E fill:#e8f5e8 +``` + +#### 3. Plugin Development + +**Creating a Color Operation Plugin:** + +```cpp +class MyColorOp : public plugin::ColourOpPlugin { +public: + MyColorOp(caf::actor_config& cfg) : ColourOpPlugin(cfg) {} + + // Implement color transformation + void apply_colour_operation( + ImageBufPtr& image, + const ColourOperationDataPtr& op_data + ) override { + // Your color processing here + process_pixels(image->buffer(), op_data); + } + +private: + void process_pixels(uint8_t* pixels, const ColourOperationDataPtr& data); +}; + +// Register plugin +extern "C" { + plugin::PluginFactoryPtr plugin_factory() { + return std::make_shared( + plugin::PluginType::ColourOp, + "MyColorOp", + []() { return std::make_shared(); } + ); + } +} +``` + +### Building New Features + +#### 1. Adding a New Actor + +1. **Create header file** in `include/xstudio/myfeature/`: +```cpp +class MyFeatureActor : public caf::event_based_actor { +public: + MyFeatureActor(caf::actor_config& cfg); + caf::behavior make_behavior() override; +private: + caf::behavior behavior_; +}; +``` + +2. **Implement in** `src/myfeature/src/`: +```cpp +MyFeatureActor::MyFeatureActor(caf::actor_config& cfg) + : caf::event_based_actor(cfg) { + + // Register with system + system().registry().put("MYFEATURE", this); + + behavior_.assign( + [=](my_action_atom, const std::string& data) { + // Handle your custom message + return process_action(data); + } + ); +} +``` + +3. **Add CMakeLists.txt** and **integrate with build system** + +#### 2. Creating UI Components + +**QML Component:** +```qml +// MyFeaturePanel.qml +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import xStudio 1.0 + +Rectangle { + id: root + + property alias myFeatureModel: model + + MyFeatureModel { + id: model + backend: app_window.session + } + + Column { + Button { + text: "My Action" + onClicked: model.performAction() + } + + ListView { + model: model + delegate: Text { text: model.displayName } + } + } +} +``` + +**C++ Backend Model:** +```cpp +class MyFeatureModel : public QAbstractListModel { + Q_OBJECT +public: + explicit MyFeatureModel(QObject* parent = nullptr); + + Q_INVOKABLE void performAction(); + + // QAbstractListModel interface + int rowCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) const override; + +private: + caf::actor backend_; + std::vector data_; +}; +``` + +### Testing Your Changes + +#### Unit Tests +```bash +# Build with testing enabled +cmake .. -DBUILD_TESTING=ON +make -j$(nproc) + +# Run specific tests +./src/myfeature/test/myfeature_test + +# Run all C++ tests +make test +``` + +#### Python API Tests +```bash +cd python/test +pytest test_myfeature.py -v +``` + +#### Integration Testing +```bash +# Start xSTUDIO in test mode +./bin/xstudio.bin --session test_session -e -n + +# Run Python integration tests +python integration_test.py +``` + +### Code Style and Standards + +#### C++ Guidelines +- **Modern C++17** features encouraged +- **RAII** patterns for resource management +- **Smart pointers** over raw pointers +- **const-correctness** throughout +- **CAF message passing** for actor communication + +#### Formatting +```bash +# Format C++ code (if enabled) +ninja clangformat + +# Check with clang-tidy +ninja clang-tidy +``` + +#### Commit Guidelines +```bash +# Example commit message +git commit -m "feat(playhead): add frame-accurate seeking + +- Implement sub-frame positioning +- Add timeline scrubbing support +- Update playback controls UI +- Add unit tests for seek accuracy + +Closes #123" +``` + +## 🔌 Python API + +The Python API provides complete access to xSTUDIO's functionality: + +### Basic Usage + +```python +import xstudio + +# Connect to running xSTUDIO instance +connection = xstudio.Connection() +connection.connect_remote("localhost", 45500) + +# Create new session +session = connection.create_session("My Project") + +# Add media to playlist +media = session.create_media("/path/to/video.mov") +playlist = session.create_playlist("Shots") +playlist.add_media(media) + +# Control playback +playhead = session.playhead +playhead.play() +playhead.seek_to_frame(100) + +# Add annotations +annotation = session.create_annotation("Review note") +annotation.set_frame(100) +annotation.set_text("Fix this shot") +``` + +### Advanced Pipeline Integration + +```python +# Custom media processing pipeline +class RenderReviewPipeline: + def __init__(self, xstudio_connection): + self.xs = xstudio_connection + + def process_shots(self, shot_list): + session = self.xs.create_session("Render Review") + + for shot in shot_list: + # Create media from render path + media = session.create_media(shot.render_path) + + # Apply color correction + if shot.lut_path: + media.apply_lut(shot.lut_path) + + # Add to appropriate playlist + playlist = session.get_playlist(shot.sequence) + playlist.add_media(media) + + # Auto-generate thumbnails + media.generate_thumbnail() + + return session +``` + +### CLI Tools + +```bash +# Control running xSTUDIO instance +xstudio-control --host localhost --port 45500 \ + --command "session.playhead.play()" + +# Inject Python scripts +xstudio-inject --script my_pipeline.py + +# Batch operations +xstudio-control --batch << EOF +session = create_session("Batch") +for f in glob.glob("*.mov"): + add_media(f) +EOF +``` + +## 🔧 Configuration & Customization + +### Preferences System + +xSTUDIO uses JSON-based configuration stored in `share/preference/`: + +```json +// core_playhead.json +{ + "playback": { + "fps": 24.0, + "loop_mode": "LOOP", + "audio_enabled": true + }, + "cache": { + "max_frames": 1000, + "preroll_frames": 10 + } +} +``` + +### Custom Hotkeys + +```json +// ui_viewport.json +{ + "hotkeys": { + "space": "play_pause", + "j": "step_backward", + "k": "step_forward", + "home": "go_to_start", + "end": "go_to_end" + } +} +``` + +### Plugin Configuration + +```json +// plugin_color_pipeline_ocio.json +{ + "ocio_config": "/studio/config/aces_1.2/config.ocio", + "default_view": "sRGB", + "working_space": "ACEScg" +} +``` + +## 🐛 Troubleshooting + +### Common Issues + +#### Build Problems + +**CMake can't find Qt5:** +```bash +# Set Qt5 path explicitly +export CMAKE_PREFIX_PATH=/usr/lib/x86_64-linux-gnu/cmake/Qt5 +``` + +**Missing OpenGL drivers:** +```bash +# Install Mesa drivers (Ubuntu) +sudo apt install mesa-utils libgl1-mesa-dev + +# Test OpenGL +glxinfo | grep "direct rendering" +``` + +#### Runtime Issues + +**xSTUDIO won't start:** +```bash +# Check dependencies +ldd ./bin/xstudio.bin + +# Run with debug logging +./bin/xstudio.bin --log-level debug --log-file xstudio.log +``` + +**Poor playback performance:** +```bash +# Check GPU usage +nvidia-smi # For NVIDIA GPUs +glxinfo | grep "renderer" + +# Adjust cache settings in preferences +# Increase memory limits in core_cache.json +``` + +**Plugin loading fails:** +```bash +# Check plugin paths +export XSTUDIO_PLUGIN_PATH=/path/to/plugins + +# Verify plugin dependencies +ldd /path/to/plugin.so +``` + +### Debug Mode + +```bash +# Enable debug features +cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON + +# Run with debugger +gdb ./bin/xstudio.bin +(gdb) run --log-level debug +``` + +### Log Analysis + +```bash +# View log files +tail -f xstudio.log + +# Filter specific components +grep "MediaReader" xstudio.log +grep "ERROR" xstudio.log +``` + +## 🤝 Contributing + +We welcome contributions! Here's how to get started: + +### Development Setup + +1. **Fork the repository** and clone your fork +2. **Create a feature branch**: `git checkout -b feature/my-feature` +3. **Set up development environment**: + ```bash + mkdir build-dev && cd build-dev + cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON + make -j$(nproc) + ``` + +### Contribution Guidelines + +- **Follow the coding standards** (see [CONTRIBUTING.md](CONTRIBUTING.md)) +- **Write comprehensive tests** for new features +- **Update documentation** for API changes +- **Sign the CLA** (required for all contributions) + +### Pull Request Process + +1. **Ensure all tests pass**: `make test && pytest python/test/` +2. **Run code formatting**: `ninja clangformat` +3. **Update CHANGELOG.md** with your changes +4. **Create detailed PR description** with: + - What the change does + - Why it's needed + - How to test it + - Any breaking changes + +## 📚 Documentation + +- **[User Guide](share/docs/index.html)** - Complete user documentation +- **[API Reference](docs/api/)** - C++ API documentation +- **[Python API](docs/python_api/)** - Python bindings documentation +- **[Build Guides](docs/build_guides/)** - Platform-specific build instructions +- **[Plugin Development](docs/plugin_dev.md)** - Creating custom plugins + +## 📄 License + +xSTUDIO is licensed under the [Apache License 2.0](LICENSE). + +## 🙏 Acknowledgments + +- **Academy Software Foundation** for project stewardship +- **DNEG** for the original development and open-sourcing +- **Contributors** who have helped improve xSTUDIO +- **Open-source projects** that xSTUDIO builds upon: + - [C++ Actor Framework (CAF)](https://actor-framework.org/) + - [Qt Framework](https://qt.io/) + - [OpenEXR](https://openexr.com/) + - [FFmpeg](https://ffmpeg.org/) + - [pybind11](https://pybind11.readthedocs.io/) + +## 📞 Support + +- **GitHub Issues**: [Report bugs or request features](https://github.com/AcademySoftwareFoundation/xstudio/issues) +- **Discussions**: [Community discussions](https://github.com/AcademySoftwareFoundation/xstudio/discussions) +- **Documentation**: [User guides and API docs](docs/) + +--- + +
+ +**Built with ❤️ for the Film & VFX Community** + +[🌟 Star us on GitHub](https://github.com/AcademySoftwareFoundation/xstudio) | [🐛 Report Issues](https://github.com/AcademySoftwareFoundation/xstudio/issues) | [💬 Discussions](https://github.com/AcademySoftwareFoundation/xstudio/discussions) + +
\ No newline at end of file diff --git a/include/xstudio/http_client/http_client_actor.hpp b/include/xstudio/http_client/http_client_actor.hpp index 3210294cf..cd7de7fe1 100644 --- a/include/xstudio/http_client/http_client_actor.hpp +++ b/include/xstudio/http_client/http_client_actor.hpp @@ -47,6 +47,7 @@ namespace http_client { inline static const std::string NAME = "HTTPWorker"; caf::behavior make_behavior() override { return behavior_; } std::string get_error_string(const httplib::Error err); + bool is_safe_url(const std::string &scheme_host_port); private: caf::behavior behavior_; diff --git a/include/xstudio/plugin_manager/plugin_manager.hpp b/include/xstudio/plugin_manager/plugin_manager.hpp index 2b7e03a37..0e7f0971d 100644 --- a/include/xstudio/plugin_manager/plugin_manager.hpp +++ b/include/xstudio/plugin_manager/plugin_manager.hpp @@ -105,6 +105,7 @@ namespace plugin_manager { [[nodiscard]] std::string spawn_menu_ui(const utility::Uuid &uuid); private: + bool is_safe_plugin_path(const std::string &path); std::list plugin_paths_; std::map factories_; }; diff --git a/run.sh b/run.sh new file mode 100755 index 000000000..2d553c810 --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +docker run --network=host --volume=echo ~:/home/${USER} --user=id -u ${USER} --env="DISPLAY" --volume="/etc/passwd:/etc/passwd:ro" dneg/xstudio + diff --git a/run2.sh b/run2.sh new file mode 100755 index 000000000..cc5deab2b --- /dev/null +++ b/run2.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# see: https://stackoverflow.com/questions/16296753/can-you-run-gui-applications-in-a-linux-docker-container +# to get display from docker container to host + + +XSOCK=/tmp/.X11-unix/X50 +XAUTH=/tmp/.docker.xauth + +xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - +export QT_GRAPHICSSYSTEM=native +export QT_X11_NO_MITSHM=1 + + + +docker run -it -v $XSOCK:$XSOCK -v /home/ling/.Xauthority:/root/.Xauthority:ro -e XAUTHORIY=/root/.Xauthority -e DISPLAY=$DISPLAY --net=host dneg/xstudio bash diff --git a/src/embedded_python/src/embedded_python.cpp b/src/embedded_python/src/embedded_python.cpp index d4df3741a..57338f774 100644 --- a/src/embedded_python/src/embedded_python.cpp +++ b/src/embedded_python/src/embedded_python.cpp @@ -142,15 +142,47 @@ XSTUDIO = Connection( void EmbeddedPython::hello() const { py::print("Hello, World!"); } -void EmbeddedPython::exec(const std::string &pystring) const { py::exec(pystring); } +void EmbeddedPython::exec(const std::string &pystring) const { + // Basic input validation to prevent obvious injection attempts + if (pystring.find("__import__") != std::string::npos || + pystring.find("eval(") != std::string::npos || + pystring.find("exec(") != std::string::npos || + pystring.find("compile(") != std::string::npos || + pystring.find("open(") != std::string::npos || + pystring.find("file(") != std::string::npos || + pystring.size() > 10000) { // Limit string size + throw std::runtime_error("Potentially unsafe Python code detected"); + } + py::exec(pystring); +} void EmbeddedPython::eval_file(const std::string &pyfile) const { py::eval_file(pyfile); } nlohmann::json EmbeddedPython::eval(const std::string &pystring) const { + // Basic input validation to prevent obvious injection attempts + if (pystring.find("__import__") != std::string::npos || + pystring.find("eval(") != std::string::npos || + pystring.find("exec(") != std::string::npos || + pystring.find("compile(") != std::string::npos || + pystring.find("open(") != std::string::npos || + pystring.find("file(") != std::string::npos || + pystring.size() > 10000) { // Limit string size + throw std::runtime_error("Potentially unsafe Python code detected"); + } return py::eval(pystring); } nlohmann::json EmbeddedPython::eval(const std::string &pystring, const nlohmann::json &locals) const { + // Basic input validation to prevent obvious injection attempts + if (pystring.find("__import__") != std::string::npos || + pystring.find("eval(") != std::string::npos || + pystring.find("exec(") != std::string::npos || + pystring.find("compile(") != std::string::npos || + pystring.find("open(") != std::string::npos || + pystring.find("file(") != std::string::npos || + pystring.size() > 10000) { // Limit string size + throw std::runtime_error("Potentially unsafe Python code detected"); + } py::object pylocal = locals; return py::eval(pystring, py::globals(), pylocal); diff --git a/src/http_client/src/http_client_actor.cpp b/src/http_client/src/http_client_actor.cpp index 12de88a35..e458fabb7 100644 --- a/src/http_client/src/http_client_actor.cpp +++ b/src/http_client/src/http_client_actor.cpp @@ -60,6 +60,38 @@ std::string HTTPWorker::get_error_string(const httplib::Error err) { return "Unknown"; } +bool HTTPWorker::is_safe_url(const std::string &scheme_host_port) { + // Basic URL validation to prevent SSRF attacks + if (scheme_host_port.empty()) { + return false; + } + + // Block localhost and private IPs + if (scheme_host_port.find("localhost") != std::string::npos || + scheme_host_port.find("127.0.0.1") != std::string::npos || + scheme_host_port.find("0.0.0.0") != std::string::npos || + scheme_host_port.find("::1") != std::string::npos || + scheme_host_port.find("10.") != std::string::npos || + scheme_host_port.find("192.168.") != std::string::npos || + scheme_host_port.find("172.16.") != std::string::npos || + scheme_host_port.find("172.17.") != std::string::npos || + scheme_host_port.find("172.18.") != std::string::npos || + scheme_host_port.find("172.19.") != std::string::npos || + scheme_host_port.find("172.2") != std::string::npos || + scheme_host_port.find("172.30.") != std::string::npos || + scheme_host_port.find("172.31.") != std::string::npos) { + return false; + } + + // Only allow https and http schemes + if (scheme_host_port.find("https://") != 0 && + scheme_host_port.find("http://") != 0) { + return false; + } + + return true; +} + HTTPWorker::HTTPWorker( caf::actor_config &cfg, time_t connection_timeout, @@ -75,6 +107,9 @@ HTTPWorker::HTTPWorker( const std::string &body, const std::string &content_type) -> result { try { + if (!is_safe_url(scheme_host_port)) { + return make_error(hce::connection_error, "Unsafe URL blocked for security"); + } httplib::Client cli(scheme_host_port.c_str()); cli.set_follow_location(true); cli.set_connection_timeout(connection_timeout, 0); @@ -203,6 +238,9 @@ HTTPWorker::HTTPWorker( const std::string &body, const std::string &content_type) -> result { try { + if (!is_safe_url(scheme_host_port)) { + return make_error(hce::connection_error, "Unsafe URL blocked for security"); + } httplib::Client cli(scheme_host_port.c_str()); cli.set_follow_location(true); cli.set_connection_timeout(connection_timeout, 0); @@ -266,6 +304,9 @@ HTTPWorker::HTTPWorker( const std::string &body, const std::string &content_type) -> result { try { + if (!is_safe_url(scheme_host_port)) { + return make_error(hce::connection_error, "Unsafe URL blocked for security"); + } httplib::Client cli(scheme_host_port.c_str()); cli.set_follow_location(true); cli.set_connection_timeout(connection_timeout, 0); diff --git a/src/launch/xstudio/src/xstudio.cpp b/src/launch/xstudio/src/xstudio.cpp index 76501dc46..88c23d2eb 100644 --- a/src/launch/xstudio/src/xstudio.cpp +++ b/src/launch/xstudio/src/xstudio.cpp @@ -102,23 +102,27 @@ struct ExitTimeoutKiller { spdlog::debug("ExitTimeoutKiller start ignored"); } #else - - - // lock the mutex ... - clean_actor_system_exit.lock(); - - // .. and start a thread to watch the mutex - exit_timeout = std::thread([&]() { - // wait for stop() to be called - 10s - if (!clean_actor_system_exit.try_lock_for(std::chrono::seconds(10))) { - // stop() wasn't called! Probably failed to exit actor_system, - // see main() function. Kill process. - spdlog::critical("xSTUDIO has not exited cleanly: killing process now"); - kill(0, SIGKILL); - } else { - clean_actor_system_exit.unlock(); - } - }); + try { + // lock the mutex ... + clean_actor_system_exit.lock(); + + // .. and start a thread to watch the mutex + exit_timeout = std::thread([&]() { + // wait for stop() to be called - 10s + if (!clean_actor_system_exit.try_lock_for(std::chrono::seconds(10))) { + // stop() wasn't called! Probably failed to exit actor_system, + // see main() function. Kill process. + spdlog::critical("xSTUDIO has not exited cleanly: killing process now"); + kill(0, SIGKILL); + } else { + clean_actor_system_exit.unlock(); + } + }); + } catch (...) { + // Ensure mutex is unlocked if thread creation fails + clean_actor_system_exit.unlock(); + throw; + } } #endif @@ -1012,18 +1016,21 @@ int main(int argc, char **argv) { // Add a CafSystemObject to the application - this is QObject that simply // holds a reference to the actor system so that we can access the system // in Qt main loop - new CafSystemObject(&app, system); + auto caf_system_obj = new CafSystemObject(&app, system); + Q_UNUSED(caf_system_obj); // Explicitly mark as used to prevent compiler warnings const QUrl url( l.actions["reskin"] ? QStringLiteral("qrc:/main_reskin.qml") : QStringLiteral("qrc:/main.qml")); QQmlApplicationEngine engine; + // These image providers will be owned by the engine and cleaned up automatically engine.addImageProvider(QLatin1String("thumbnail"), new ThumbnailProvider); engine.addImageProvider(QLatin1String("shotgun"), new ShotgunProvider); engine.rootContext()->setContextProperty( "applicationDirPath", QGuiApplication::applicationDirPath()); + // Helper and studio objects have parent QObjects and will be cleaned up automatically auto helper = new Helpers(&engine); engine.rootContext()->setContextProperty("helpers", helper); diff --git a/src/plugin/hud/pixel_probe/src/pixel_probe.cpp b/src/plugin/hud/pixel_probe/src/pixel_probe.cpp index 0bca83786..0970a26b2 100644 --- a/src/plugin/hud/pixel_probe/src/pixel_probe.cpp +++ b/src/plugin/hud/pixel_probe/src/pixel_probe.cpp @@ -272,7 +272,7 @@ void PixelProbeHUD::make_pixel_info_onscreen_text(const media_reader::PixelInfo if (is_code_value) { char buf[128]; for (size_t i = 0; i < info.size(); ++i) { - sprintf(buf, "%d", info[i].code_value); + snprintf(buf, sizeof(buf), "%d", info[i].code_value); int l = strlen(buf); while (l < 4) { ss << " "; @@ -284,10 +284,10 @@ void PixelProbeHUD::make_pixel_info_onscreen_text(const media_reader::PixelInfo } } else { char prc[128]; - sprintf(prc, "%%.%df", precision); + snprintf(prc, sizeof(prc), "%%.%df", precision); char buf[128]; for (size_t i = 0; i < info.size(); ++i) { - sprintf(buf, prc, info[i].pixel_value); + snprintf(buf, sizeof(buf), prc, info[i].pixel_value); ss << buf; if (i != (info.size() - 1)) ss << ", "; diff --git a/src/plugin_manager/src/plugin_manager.cpp b/src/plugin_manager/src/plugin_manager.cpp index c2d5c28e3..489955854 100644 --- a/src/plugin_manager/src/plugin_manager.cpp +++ b/src/plugin_manager/src/plugin_manager.cpp @@ -44,12 +44,42 @@ std::string GetLastErrorAsString() { PluginManager::PluginManager(std::list plugin_paths) : plugin_paths_(std::move(plugin_paths)) {} +bool PluginManager::is_safe_plugin_path(const std::string &path) { + // Convert to absolute path to prevent path traversal attacks + std::error_code ec; + auto absolute_path = fs::absolute(path, ec); + if (ec) { + return false; + } + + // Block paths containing dangerous components + std::string path_str = absolute_path.string(); + if (path_str.find("..") != std::string::npos || + path_str.find("/tmp") == 0 || + path_str.find("/var/tmp") == 0 || + path_str.find("//") != std::string::npos) { + return false; + } + + // Only allow certain safe directories (adjust these as needed) + auto safe_root = xstudio_root("/plugin"); + return path_str.find("/usr/local/lib/xstudio") == 0 || + path_str.find("/opt/xstudio") == 0 || + path_str.find(safe_root) == 0; +} + size_t PluginManager::load_plugins() { // scan for .so or .dll for each path. size_t loaded = 0; spdlog::debug("Loading Plugins"); for (const auto &path : plugin_paths_) { + // Validate plugin path for security + if (!is_safe_plugin_path(path)) { + spdlog::warn("Skipping unsafe plugin path: {}", path); + continue; + } + try { // read dir content.. for (const auto &entry : fs::directory_iterator(path)) { diff --git a/src/pyside2_module/src/threaded_viewport.cpp b/src/pyside2_module/src/threaded_viewport.cpp index 62c7268a7..d8b76615a 100644 --- a/src/pyside2_module/src/threaded_viewport.cpp +++ b/src/pyside2_module/src/threaded_viewport.cpp @@ -117,7 +117,18 @@ void ThreadedViewportWindow::draw() { glBufferData(GL_PIXEL_UNPACK_BUFFER, SIZE, 0, GL_STREAM_DRAW); void *mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - memcpy(mappedBuffer, &m_frames[m_index][0], SIZE); + if (mappedBuffer != nullptr) { + memcpy(mappedBuffer, &m_frames[m_index][0], SIZE); + } else { + // Handle mapping error - log or fall back to glTexImage2D with data + GLenum error = glGetError(); + qWarning("glMapBuffer failed with error: %d", error); + // Fall back to direct texture upload + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, &m_frames[m_index][0]); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); // Unbind PBO + return; + } glBindTexture(GL_TEXTURE_2D, m_texture); diff --git a/src/session/src/session_actor.cpp b/src/session/src/session_actor.cpp index fc27d7101..ffd16b2aa 100644 --- a/src/session/src/session_actor.cpp +++ b/src/session/src/session_actor.cpp @@ -1045,7 +1045,7 @@ caf::message_handler SessionActor::message_handler() { for (const auto &d : bd) { if (d.owner_ && d.logical_start_frame_) { caf::actor owner = d.owner_->actor(); - sprintf(buf.data(), path.c_str(), idx); + snprintf(buf.data(), buf.size(), path.c_str(), idx); caf::uri u = posix_path_to_uri(buf.data()); request( offscreen_renderer, diff --git a/src/thumbnail/src/thumbnail.cpp b/src/thumbnail/src/thumbnail.cpp index e51829376..4e13268df 100644 --- a/src/thumbnail/src/thumbnail.cpp +++ b/src/thumbnail/src/thumbnail.cpp @@ -158,7 +158,9 @@ void quickSquash( if (is_pow_2_width_ratio) { auto *squashed = new float[inWidth * inHeight * nchans]; - memcpy(squashed, inBuffer, inWidth * inHeight * nchans * sizeof(float)); + // Validate input buffer size before copy + size_t expected_input_size = inWidth * inHeight * nchans * sizeof(float); + memcpy(squashed, inBuffer, expected_input_size); int width = inWidth; while (width != outWidth) { @@ -277,7 +279,9 @@ void quickSquash( delete[] old_squashed; height /= 2; } - memcpy(outBuffer, squashed, outHeight * outWidth * nchans * sizeof(float)); + // Validate output buffer size before copy + size_t expected_output_size = outHeight * outWidth * nchans * sizeof(float); + memcpy(outBuffer, squashed, expected_output_size); delete[] squashed; return; } @@ -428,7 +432,9 @@ void quickSquash( if (is_pow_2_width_ratio) { auto *squashed = new uint8_t[inWidth * inHeight * nchans]; - memcpy(squashed, inBuffer, inWidth * inHeight * nchans * sizeof(uint8_t)); + // Validate input buffer size before copy + size_t expected_input_size = inWidth * inHeight * nchans * sizeof(uint8_t); + memcpy(squashed, inBuffer, expected_input_size); int width = inWidth; while (width != outWidth) { @@ -539,7 +545,9 @@ void quickSquash( delete[] old_squashed; height /= 2; } - memcpy(outBuffer, squashed, outWidth * outHeight * nchans); + // Validate output buffer size before copy + size_t expected_output_size = outWidth * outHeight * nchans; + memcpy(outBuffer, squashed, expected_output_size); delete[] squashed; return; } diff --git a/src/ui/opengl/src/gl_debug_utils.cpp b/src/ui/opengl/src/gl_debug_utils.cpp index 8435e456c..0e0bfdca2 100644 --- a/src/ui/opengl/src/gl_debug_utils.cpp +++ b/src/ui/opengl/src/gl_debug_utils.cpp @@ -24,7 +24,7 @@ void grab_framebuffer_to_disk() { std::array nm; static int fnum = 1; - sprintf(nm.data(), "/user_data/.tmp/xstudio_viewport.%04d.exr", fnum++); + snprintf(nm.data(), nm.size(), "/user_data/.tmp/xstudio_viewport.%04d.exr", fnum++); Imf::RgbaOutputFile out( nm.data(), Imath::Box2i(Imath::V2i(0, 0), Imath::V2i(viewport[2] - 1, viewport[3] - 1))); diff --git a/src/ui/opengl/src/texture.cpp b/src/ui/opengl/src/texture.cpp index 1ffb77ffd..184d7e5c1 100644 --- a/src/ui/opengl/src/texture.cpp +++ b/src/ui/opengl/src/texture.cpp @@ -246,16 +246,27 @@ void GLBlindRGBA8bitTex::pixel_upload() { const int n_threads = 8; // TODO: proper thread count here std::vector memcpy_threads; size_t sz = std::min(tex_size_bytes(), new_source_frame_->size()); - size_t step = ((sz / n_threads) / 4096) * 4096; - auto *dst = (uint8_t *)new_source_frame_->buffer(); - - uint8_t *ioPtrY = buffer_io_ptr_; - - for (int i = 0; i < n_threads; ++i) { - memcpy_threads.emplace_back(memcpy, ioPtrY, dst, std::min(sz, step)); - ioPtrY += step; - dst += step; - sz -= step; + + // Ensure minimum step size and proper alignment + size_t step = std::max(size_t(4096), ((sz / n_threads) / 4096) * 4096); + if (step == 0 || sz < step) { + // Fallback to single-threaded copy for small sizes + memcpy((uint8_t *)new_source_frame_->buffer(), buffer_io_ptr_, sz); + } else { + auto *dst = (uint8_t *)new_source_frame_->buffer(); + uint8_t *ioPtrY = buffer_io_ptr_; + + for (int i = 0; i < n_threads && sz > 0; ++i) { + size_t copy_size = std::min(sz, step); + if (copy_size > 0) { + memcpy_threads.emplace_back([ioPtrY, dst, copy_size]() { + memcpy(dst, ioPtrY, copy_size); + }); + ioPtrY += copy_size; + dst += copy_size; + sz -= copy_size; + } + } } // ensure any threads still running to copy data to this texture are done diff --git a/src/utility/src/edit_list.cpp b/src/utility/src/edit_list.cpp index df717851b..7ed4da4cf 100644 --- a/src/utility/src/edit_list.cpp +++ b/src/utility/src/edit_list.cpp @@ -6,6 +6,7 @@ #endif #include #include +#include #include "xstudio/utility/edit_list.hpp" #include "xstudio/utility/logging.hpp" @@ -566,6 +567,11 @@ EditListSection EditList::next_section( "EditList::next_section error: ref time is at or beyond last section."); } + // Avoid integer overflow by checking size bounds before casting + if (sl_.size() > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("EditList size exceeds maximum integer value"); + } + const int skip_to_index = std::max( int(0), std::min(static_cast(sl_.size() - 1), current_section_index + skip_sections)); diff --git a/src/utility/src/helpers.cpp b/src/utility/src/helpers.cpp index 471d6e333..28a9f1ff0 100644 --- a/src/utility/src/helpers.cpp +++ b/src/utility/src/helpers.cpp @@ -320,10 +320,28 @@ std::string xstudio::utility::uri_decode(const std::string &eString) { unsigned int i, j; for (i = 0; i < eString.length(); i++) { if (int(eString[i]) == 37) { - sscanf(eString.substr(i + 1, 2).c_str(), "%x", &j); - ch = static_cast(j); - ret += ch; - i = i + 2; + // Ensure we have at least 2 more characters for hex decoding + if (i + 2 >= eString.length()) { + // Invalid encoding, skip malformed sequence + ret += eString[i]; + continue; + } + auto hex_str = eString.substr(i + 1, 2); + // Validate hex characters + if (hex_str.length() == 2 && + std::isxdigit(hex_str[0]) && std::isxdigit(hex_str[1])) { + if (sscanf(hex_str.c_str(), "%x", &j) == 1) { + ch = static_cast(j); + ret += ch; + i = i + 2; + } else { + // sscanf failed, treat as literal + ret += eString[i]; + } + } else { + // Invalid hex characters, treat as literal + ret += eString[i]; + } } else { ret += eString[i]; }